Class Remapper
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 Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionfinal @NotNull MappingLookupObtain theMappingLookupinstance from which thisRemappersources all source to destination namespace name mappings.static @NotNull StringgetRemappedFieldDescriptor(@NotNull MappingLookup lookup, @NotNull String fieldDesc, @NotNull StringBuilder sharedBuilder) Remaps a field descriptor.static @NotNull StringgetRemappedMethodDescriptor(@NotNull MappingLookup lookup, @NotNull String methodDesc, @NotNull StringBuilder sharedBuilder) Remaps a method descriptor.static @NotNull StringremapInternalName(@NotNull MappingLookup lookup, @NotNull String internalName, @NotNull StringBuilder sharedStringBuilder) Remap an internal name or arrayString, meaning that this method accepts the same kind of strings asType.getObjectType(String).@NotNull RemapperremapNode(@NotNull String owner, @NotNull org.objectweb.asm.tree.FieldNode field, @NotNull StringBuilder sharedStringBuilder) Remap aFieldNode, modifying it and it's contents.@NotNull RemapperremapNode(@NotNull String owner, @NotNull org.objectweb.asm.tree.MethodNode method, @NotNull StringBuilder sharedStringBuilder) Remap aMethodNode, modifying it and it's contents.@NotNull RemapperremapNode(@NotNull org.objectweb.asm.tree.ClassNode node, @NotNull StringBuilder sharedBuilder) Remap aClassNode, modifying it and it's contents.static booleanremapSignature(@NotNull MappingLookup lookup, @NotNull String signature, int start, int end, @NotNull StringBuilder signatureOut) Remap a generic signature string, as used for example inMethodNode.signature,FieldNode.signatureorClassNode.signature.static booleanremapSignature(@NotNull MappingLookup lookup, @NotNull String signature, @NotNull StringBuilder out) Remap a generic signature string, as used for example inMethodNode.signature,FieldNode.signatureorClassNode.signature.
-
Constructor Details
-
Remapper
Constructor. Create aRemapperinstance which uses a givenMappingLookupinstance 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- TheMappingLookupto use in order to remap the descriptor.fieldDesc- The old (unmapped) field descriptorsharedBuilder- 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 withmethod signatures.- Parameters:
lookup- TheMappingLookupto use in order to remap the descriptor.methodDesc- The old (unmapped) method descriptorsharedBuilder- 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 arrayString, meaning that this method accepts the same kind of strings asType.getObjectType(String).The contents of the
StringBuilderinstance 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 theStringBuilderis equal to the returnedString.- Parameters:
lookup- TheMappingLookupto use in order to remap the descriptor.internalName- The internal name in the source namespace.sharedStringBuilder- A sharedStringBuilderinstance 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 inMethodNode.signature,FieldNode.signatureorClassNode.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, useremapInternalName(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
codepointswhich should be remapped and pushed to theStringBuilderbuffer. 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 anOOM situation. Furthermore, ifenddoes 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 orOutOfMemoryError. As similar behaviour also applies tostart, bothstartandendshould be chosen carefully. More often than not, this method can be considered overkill and insteadremapSignature(MappingLookup, String, StringBuilder)can be used safely as an alternative - however that method will remap the entire signature while this method can (ifstartandendare chosen accordingly) remap parts of it.- Parameters:
lookup- TheMappingLookupto 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- TheStringBuilderinstance 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 inMethodNode.signature,FieldNode.signatureorClassNode.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, useremapInternalName(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- TheMappingLookupto use in order to remap the descriptor.signature- The signature to remap in the source namespace.out- TheStringBuilderinstance 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()ofoutwill be equal tosignature.
-
getLookup
Obtain theMappingLookupinstance from which thisRemappersources all source to destination namespace name mappings. This instance is set through the constructor.- Returns:
- The
MappingLookupinstance used by thisRemapper.
-
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 aClassNode, modifying it and it's contents.Warning: The given
StringBuilderinstance may be overwritten. The contents of theStringBuilderbefore the method call are completely irrelevant, and the contents after the call may be garbage. The reasonRemapperallows supplyingStringBuilderinstances is mostly for performance reasons in order to cut down the amount of times aStringBuilderinstance is allocated only to be instantly discarded again.- Parameters:
node- The node to remapsharedBuilder- TheStringBuilderinstance to use for string manipulation.- Returns:
- The current
Remapperinstance, 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 aFieldNode, modifying it and it's contents.Warning: The given
StringBuilderinstance may be overwritten. The contents of theStringBuilderbefore the method call are completely irrelevant, and the contents after the call may be garbage. The reasonRemapperallows supplyingStringBuilderinstances is mostly for performance reasons in order to cut down the amount of times aStringBuilderinstance 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 aninternal namefield- The node to remapsharedStringBuilder- TheStringBuilderinstance to use for string manipulation.- Returns:
- The current
Remapperinstance, 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 aMethodNode, modifying it and it's contents.Warning: The given
StringBuilderinstance may be overwritten. The contents of theStringBuilderbefore the method call are completely irrelevant, and the contents after the call may be garbage. The reasonRemapperallows supplyingStringBuilderinstances is mostly for performance reasons in order to cut down the amount of times aStringBuilderinstance 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 aninternal namemethod- The node to remapsharedStringBuilder- TheStringBuilderinstance to use for string manipulation.- Returns:
- The current
Remapperinstance, for chaining
-