Tapestry Training -- From The Source

Let me help you get your team up to speed in Tapestry ... fast. Visit howardlewisship.com for details on training, mentoring and support!

Sunday, October 02, 2005

Ouch! Class.getDeclaredMethods() works differently in different JDKs

This one is a bit of a pain. In Tapestry, your page and component classes tend to be abstract, with Tapestry providing a subclass, filling in additional methods, fields, constructors and implemented interfaces.

Because of that, it's necessary to duplicate the checks normally done by the compiler ... to check that all abstract methods inherited from base classes actually end up with implementations in the enhanced subclass (that is, the subclass that Tapestry creates).

At the core of this is code that finds all the non-abstract methods in the enhanced class, and up its inheritance chain. It then checks all abstract methods from superclasses or implemented interfaces to ensure that each one is, in fact, implemented.

The problem here is that the built-in Eclipse compiler seems to work differently than the Sun JDK compiler (this is for JDK 1.5).

Under Eclipse, an abstract class that implements an interface does not report those interface methods from getDeclaredMethods(). The only methods reported are the ones in the actual code for the class.

Under Sun JDK, an abstract class will report those interface methods, even if it does not provide implementations for them.

Now, I have tests for this class that I usually run from Eclipse. In fact, this was triggered by a specific bug concerning methods inherited from interfaces; the code is broken into two parts (scanning the inheritance tree for implemented methods, and scanning the interfaces for missing methods). Under Eclipse, interface methods are not reported from getDeclaredMethods(), so we make it to phase 2.

Under Sun JDK, interface methods are reported, it looks like a non-interface abstract method with no implementation, and we fail in phase 1 -- breaking my unit test.

Sigh. This may take some hackery to work in Eclipse and in Sun JDK.

4 comments:

Eugene Kuleshov said...

That is strange, because Eclipse runs on very same Sun's JDK. Maybe it has something to do with either Eclipse compiler, class loaders or the fact that Eclipse may run these tests under debugger.

Anonymous said...

Hi Howard.

If I understood you correctly, this is a very serious compatibility issue. So I created a small example, and I could not reproduce the problem. I checked with jdk 1.4.2, jdk 1.5.0_04, jdk 1.6.0.

Here's the example:

import java.lang.reflect.*;

public class DeclMeths {

public static void main(String[] args) {
Method[] methods = Foo.class.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
System.out.println(methods[i]);
}
}
}

abstract class Foo implements Runnable {

public void oneM() {}
public abstract void absM();

}


As expected, only two methods are reported, and method run is _not_.

Could you please post an example?

Thanks,
--Vladimir Sizikov
J2ME Compatibility Team ;)

Alef Arendsen said...

Also note that on different JDKs Class.getDeclaredMethods() does not guarantee the methods to be returned in a specific order. Actually, on Sun's JVM, they are returned in the reverse order as on JRockit.

Another case for writing test methods independently of each other (I was hit by this while writing some Spring tests recently).

regards,
Alef

Tom Hawtin said...

Even the Eclipse compiler should generate these synthetic methods in certain situations. They are necessary for covariant return types. Exactly when methods are generated fluctuated before 5.0 was released.

Any piece of code that depends on whether methods are duplicated after declaration in a supertype, is probably broken.