Using the Java ASM
library you can parse and modify compiled Java classes. I'm trying to make a simple class change and store everything back into the Jar
file. Example codes I find don't seem to correctly convert the ClassNode
to a byte[]
:
private static Map<String, byte[]> getClassBytes(final Map<String, ClassNode> classNodes)
{
final HashMap<String, byte[]> classesMap = new HashMap<>();
for (final ClassNode classNode : classNodes.values())
{
// TODO Get byte array from ClassNode
final ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_FRAMES);
final String key = classNode.name;
final byte[] byteArray = classWriter.toByteArray();
classesMap.put(key, byteArray);
}
return classesMap;
}
Also, in the linked example above, what is MappedClass
? I can't find its definition anywhere.
When running the code posted above, the byte[]
produced is just a default one and not the actual byte code of the class. That makes sense, there is no call for adding/setting the correct class bytes. How can that be done?
You create a new ClassWriter
object, passing in nothing relevant (a settings value, that's all). You then invoke toByteArray
on it. Using 5 seconds to think it through, you should draw the conclusion that this cannot possibly work. The actual class content you'd want to write cannot possibly be known by the classWriter instance you just made - nothing in that code flow provides it to this ClassWriter instance.
The linked code is of highly dubious quality. For example, it leaks the jar file as a resource (it isn't properly resource-guarded). It uses old API (File
) with new concepts (.stream()
), in fairly silly ways. It does the old 'catch an exception, run e.printStackTrace()
, keep going' shtick, which is extremely bad code practice. Its presence in a bare-bones example is suspect; its presence in an example that is clearly intended to be ready to go is inexcusable). It involves outdated third party deps (IOUtils
) that aren't actually needed, especially considering that this code wouldn't compile on anything less than java8, so clearly 'at least java 8' is fair game for this example.
And then, finally, yes, the thing you zoomed in on: That ClassWriter code. Which is just broken.
I think it's best to forget about this blog post. It's beyond saving.
Well, you've started out by conflating two completely unrelated things 'write a jar file' and 'produce the byte content of a class file' are unrelated, so don't mix em up. You have 2 separate jobs, so write code that does job 1, and test that. Then write code that does job 2, and test that. Then write code that uses the code you wrote for job 1 and 2 and test that, and voila.
You don't. Not how it works. You make a new one. If you really need to, once you're done, you'd use Files.move
to copy/move it 'over' the old one, thus effectively 'replacing it'.
new JarOutputStream
for example. Check this SO answer for some insights on how.
As for example this tutorial shows, ASM isn't so much an 'edit classes in place' kinda tool, it's an 'edit classes in transit' kinda tool. You make a reader which does nothing by itself, you then pile onto the reader various visitors that transform nodes as they are processed, you then hook this all up to a writer and hit the go button. Part of the point is that ASM may not even hold the entire class file in memory.
After making the class writer, you're supposed to invoke all sorts of methods on the classwriter to actually 'write' the class file. You 'write' the basic stats such as the class's name and the like, then you 'write' fields, methods, initializers, and so on.
If you want to write based on an existing class (i.e. transform something that already exists instead of making a class file from scratch), you'd make a reader, and transform as you go. That tutorial I linked shows how to do this.