Saturday, January 15, 2011

Go read org.openide.modules.PatchedPublic for some binary backwards compatibility magic

The @PatchedPublic annotation was a nice surprise. Let's see:


/**
 * Marks a method or constructor as being intended to be public.
 * Even though it is private in source code, when the class is loaded
 * through the NetBeans module system the access modifier will be set
 * to public instead. Useful for retaining binary compatibility while
 * forcing recompilations to upgrade and keeping Javadoc clean.
 */
@Retention(RetentionPolicy.CLASS)
@Target({ElementType.METHOD, ElementType.CONSTRUCTOR})
public @interface PatchedPublic {}


This is a really brilliant way to force your codebase migrate to the latest APIs while maintaining binary backwards compatibility.

The idea is that you have an old method that you want to deprecate and eventually remove:

@Deprecated
public void doSomething();

The nice thing about @Deprecated is that it's a flag that signals to the API users they really shouldn't use it, but it doesn't do much else: new code might still be written against the method and your existing codebase might still use it.

So you replace it with

@PatchedPublic

private void doSomething();


Since the method is now private, all your local code that uses the old public method will fail at compile-time and will be forced to migrate to the proper APIs. That takes care of one thing.

New API users will also be unable to use it since it's private: they'll never learn about it! The bad thing about @Deprecated is that while it gives a signal to previous users that they should migrate to something else (which is nice), it also gives a signal to new users that they might do their task using some other method -- which is really bad, as they might just disregard the deprecated warning and still use it.

So the only issue now is how do you keep running the previous binary code?

The reasoning here is that if you still have humans compiling code, they will kinda be forced to migrate to the new APIs due to the compile-time errors. So source-code compatibility might not be as important for this given method.

But a big problem is when you have binary code executing -- that code will fail at runtime!

The trick is to leverage the fact that NetBeans has its own module system that does class loading and patch that class at runtime. (This is probably something they couldn't have implemented if they depended on some external OSGi container).

Since @PatchedPublic has a CLASS retention policy, it will be part of the bytecode and thus accessible by the module system. Thus, using probably some bytecode engineering, the method will be patched at runtime to become public and the old binary code will execute just nicely.

I'm usually not a big fan of annotations (I find most of the new NetBeans annotations that replace the layer.xml as more confusing instead of simplifying) but this annotation was a nice find.

But how did I find this annotation?

Well, I'm one of those guys that still has source code compiling against methods that used to be public and I'm now trying to figure out how to update my code :-) A bitter-sweet finding.

No comments:

The Trouble with Harry time loop

I saw The Trouble with Harry (1955) a while back and it didn't have a big impression on me. But recently I rewatched it and was amazed a...