John Papastergiou
John Papastergiou

Reputation: 1019

Automatic API code generation for all methods in a compiled jar

Introduction:

I have a jar that's a java game application. I want to create a mod loader an application users can use to load code into that jar. Mods are just pieces of code, that can hook specific methods inside that jar to modify it's behavior or enhance it. Access to the source code is not possible at this stage.

My initial idea, was to create an API that will be injected at the execution of the most important methods of the jar. Users/Developers will be able to code against that API (which is essentially java interfaces). There are a lot of bytecode manipulation utilities. I selected javassist that allows me to hook before/after events on methods using higher level concepts and not just pure bytecode.

After a bit of work, I figured out that it would be easier and would provide more options to developers, if I could automatically perform the previously described operation. In short:

1)A program would iterate the classes and their methods in the jar.

2)For each class the program would generate code using a template engine (eg freemarker).That could would be one interface providing before and after methods for each method in the class and one interface manager that would hold the implementations

3)The interface manager would have the same methods and would be injected in the jar. His methods implementation will not be arbitrary though, they'd just be a proxy towards the interfaces so as the interface implementations can be dynamically loaded and executed in the jar.

Here's an example:

Say there's a class nmed Item with only method called getName() in the jar

The generator would iterate in the jar and for that class and method, it would create code

public interface ItemOverloader
{
   public void beforeGetName();
   public void afterGetName(); 
}

public ItemOverloadManager
{
  public static List<ItemOverloader> overloaders =  new ArrayList<>();

  public static void beforeGetName()
  {
     for(ItemOverloader i: overloaders)
        i.beforeGetName();
  }

  public static void afterGetName()
  {
     for(ItemOverloader i: overloaders)
        i.afterGetName();
  }
}

then the javassist library would:

CtClass itemClazz = ... bla bla
CtMethod getNameMethod = ... bla bla
getNameMethod.executeBefore("ItemOverloadManager.beforeGetName()");
getNameMethod.executeAfter("ItemOverloadManager.afterGetName()");

Question:

I'm worried that if this is performed automatically it will add code in every single method of the initial jar. Although that would succeed in fulfilling the requirement of having a hook in every single method, that code would introduce performance overhead in two ways:

1)Each method call, would look up a static class ( the manager ) and it's list of interface implementations.

2)If the manager has implementations, it would look them up in the list and then execute them.

I'm wondering how much of a performance overhead that is and if it's an acceptable way to do this. If not, what other ways exist to automatically generate API access to a great number of method calls in an application ?

In case all those hooks are present, but very few items exist in the lists of implementations, would it still downgrade the performance by a fair amount ?

Upvotes: 0

Views: 104

Answers (1)

biziclop
biziclop

Reputation: 49794

Unfortunately performance overhead is best measured and not guessed. You should of course use the best algorithms you can find and avoid obviously stupid things (like allocating huge chunks of memory in tight loops, writing really long and convoluted methods or using too many/few threads), but beyond that performance is hard to anticipate. Java in general is really good at optimising away unnecessary method calls and code with no observable effects, but the exact performance hit can only be determined through rigorous testing.

Regardless of performance, I don't think the architecture you propose is a good idea, it feels like it would be a nightmare to test and would quickly lead to class invariants being broken. You should ideally plan the extension points to your code very carefully, and not generate them for every method blindly.

A sensible compromise could be to create an annotation you can attach to methods you want to expose in your API and have an annotation processor generate the API just out of those methods.

Upvotes: 1

Related Questions