Covariant return types for bean property accessors

This afternoon a colleague helped me to track down a nasty bug that I had been struggling with all day.

Our story begins with covariant return types, a new feature added in Java 5. Consider the following contrived example:

interface Currency {
    String getIsoCurrencyCode();
}
class CurrencyEntity implements Currency {
    @Override
    public String getIsoCurrencyCode() {
        return "AUD";
    }
}
interface CurrencyAware {
    Currency getCurrency();
}
class PriceEntity implements CurrencyAware {
    private CurrencyEntity currency;
    @Override
    public CurrencyEntity getCurrency() {
        return currency;
    }
    public void setCurrency(CurrencyEntity currency) {
        this.currency = currency;
    }
}

Notice that PriceEntity implements the getCurrency method declared on the CurrencyAware interface, but the return type is different: the return type is declared as the concrete class CurrencyEntity, rather than the interface Currency which CurrencyEntity implements. Intuitively this seems reasonable, because code which calls the getCurrency method through the CurrencyAware interface will be expecting a return type of Currency, but the actual return type of CurrencyEntity implements the Currency interface, so everything works nicely. Here Java is allowing an implicit “covariant conversion” from the narrower type CurrencyEntity to its broader supertype Currency.

Things are not quite so simple when we examine the disassembled bytecode for PriceEntity however:

class PriceEntity extends java.lang.Object implements CurrencyAware{
PriceEntity();
  Code:
   0:   aload_0
   1:   invokespecial   #1; //Method java/lang/Object."<init>":()V
   4:   return
public CurrencyEntity getCurrency();
  Code:
   0:   aload_0
   1:   getfield    #2; //Field currency:LCurrencyEntity;
   4:   areturn
public void setCurrency(CurrencyEntity);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield    #2; //Field currency:LCurrencyEntity;
   5:   return
public Currency getCurrency();
  Code:
   0:   aload_0
   1:   invokevirtual   #3; //Method getCurrency:()LCurrencyEntity;
   4:   areturn
}

The striking thing here is that the class defines two nullary methods named getCurrency. At the source level this would be a compile error, because Java dispatches only on method name and argument types. I'm still not sure how the JVM decides which method to invoke, although it would make no difference since the second version of the method indirects to the first. Clearly the second method exists only to satisfy the CurrencyAware interface.

These duplicate methods give rise to some unexpected results when reflection is applied to this class, as the debugger shows:

PriceEntity.class.getDeclaredMethods()
     (java.lang.reflect.Method[]) [public CurrencyEntity PriceEntity.getCurrency(),
     public Currency PriceEntity.getCurrency(),
     void PriceEntity.setCurrency(CurrencyEntity)]
PriceEntity.class.getMethod("getCurrency", new Class[] { })
     (java.lang.reflect.Method) public CurrencyEntity PriceEntity.getCurrency()

Although getMethod gives us the expected result (namely, a Method object with return type of CurrencyEntity), getDeclaredMethods returns an array with three elements; both of the duplicate getCurrency methods are included.

These duplicate methods can confuse any other code which uses reflection to examine the class with covariant return types. A prime example is java.beans.Introspector, which iterates across all methods of a class in order to find bean property getter-setter pairs. We would expect this process to find a bean property whose type is CurrencyEntity and which is accessed through getCurrency/setCurrency. But if the duplicate getCurrency method is encountered first, the Introspector implementation in Sun’s JDK6 will erroneously identify a read-only bean property of type Currency, accessed through getCurrency with no setter.

java.beans.Introspector.getBeanInfo(PriceEntity.class).getPropertyDescriptors()
     (java.beans.PropertyDescriptor[]) [java.beans.PropertyDescriptor@e858167c,
     java.beans.PropertyDescriptor@f76288f9]
java.beans.Introspector.getBeanInfo(PriceEntity.class).getPropertyDescriptors()[1].getName()
     (java.lang.String) currency
java.beans.Introspector.getBeanInfo(PriceEntity.class).getPropertyDescriptors()[1].getReadMethod()
     (java.lang.reflect.Method) public Currency PriceEntity.getCurrency()
java.beans.Introspector.getBeanInfo(PriceEntity.class).getPropertyDescriptors()[1].getWriteMethod()
     null

The behaviour of Introspector is even more problematic in OpenJDK6 (at least in RHEL version 1.6.0.0-1.7.b09.el5) — the PropertyDescriptor returned will have a pair of accessor methods whose types are incompatible:

java.beans.Introspector.getBeanInfo(PriceEntity.class).getPropertyDescriptors()[1].getName()
     (java.lang.String) currency
java.beans.Introspector.getBeanInfo(PriceEntity.class).getPropertyDescriptors()[1].getReadMethod()
     (java.lang.reflect.Method) public Currency PriceEntity.getCurrency()
java.beans.Introspector.getBeanInfo(PriceEntity.class).getPropertyDescriptors()[1].getWriteMethod()
     (java.lang.reflect.Method) public void PriceEntity.setCurrency(CurrencyEntity)

This can then lead to some very confusing errors in code which relies on Introspector. The first clue to our bug this afternoon was the following bizarre stacktrace from Freemarker:

java.beans.IntrospectionException: type mismatch between read and write methods
        at java.beans.PropertyDescriptor.findPropertyType(PropertyDescriptor.java:657)
        at java.beans.PropertyDescriptor.setWriteMethod(PropertyDescriptor.java:318)
        at java.beans.PropertyDescriptor.<init>(PropertyDescriptor.java:140)
        at freemarker.ext.beans.BeansWrapper.populateClassMapWithBeanInfo(BeansWrapper.java:1127)
        at freemarker.ext.beans.BeansWrapper.populateClassMap(BeansWrapper.java:1017)
        at freemarker.ext.beans.BeansWrapper.introspectClassInternal(BeansWrapper.java:955)
        at freemarker.ext.beans.BeansWrapper.introspectClass(BeansWrapper.java:928)
        at freemarker.ext.beans.BeanModel.<init>(BeanModel.java:139)

Interestingly, this problem with Introspector was reported as a bug against the Spring project several years ago. As I understand it, covariant return types on bean property accessors are only supported correctly in Spring today because that library no longer relies on JavaBeans for its bean property introspection.

Abraham Lincoln: vampire hunter by Seth Grahame-Smith

[star][star][half-star]

The glass room by Simon Mawer

[star][star][half-star]

When you are engulfed in flames by David Sedaris

[star][star][star]

Kafka on the shore by Haruki Murakami

[star][star]

Annabel Scheme by Robin Sloan

[star][star][star][half-star]

Devices and desires by K. J. Parker

[star][half-star]

Found in the street by Patricia Highsmith

[star][star]

Pattern recognition by William Gibson

[star][star][star]

The coma by Alex Garland

[star][star][star][half-star]

The clown by Heinrich Böll

[star][star][half-star]

Lord of Light by Roger Zelazny

[star][star]

Kernel Conference Australia 2009

I was fortunate enough to attend the first day of Sun’s Kernel Conference Australia 2009 today (I was a last-minute substitute for a colleague who couldn’t make it).

The conference opened with a keynote by Jeff Bonwick and Bill Moore, Sun’s masterminds behind ZFS, in which they ran through a big list of the cool stuff that has been happening with ZFS lately. I thought their talk was well presented, and it definitely left me with a renewed desire to play with ZFS (if only FreeBSD or OpenSolaris would run on my hardware at home ..).

Most interesting for me was a new feature they described, called the L2ARC (Level 2 Adaptive Replacement Cache), which allows expensive but speedy flash memory to be used as a kind of midway point between cache in core memory and the much slower underlying magnetic disk devices. When cache entries are evicted from the memory cache, they can be moved to the flash device (which provides much faster seek times and throughput than a spinning disk, but with a smaller capacity).

Jeff and Bill presented some very impressive benchmark results for this new work. They compared two similarly-priced configurations: one was running a bunch of fast SAS disks, but the other was configured with a high-reliability SSD as a write cache, a less reliable but much larger SSD used for L2ARC, and a bunch of low-power, high-capacity hard disks. Their results showed the latter outperforming the former in all respects, most notably in power consumption (because it eliminates the high-RPM hard disks). I wish I could find their benchmark results online somewhere, because the numbers were impressive. This kind of configuration seems like it really would be a viable improvement for high-density storage.

The presentation by Pawel Dawidek, a FreeBSD committer, gave a run-down of GEOM(4) in FreeBSD. He went a bit overboard with the Keynote transitions, but otherwise his presentation was very accessible. Certain individuals have told me about GEOM and its advantages in the past, but when Pawel ran through the essentials of how the GEOM abstractions are put together I realised that they have a model which is very flexible, yet simple, and makes a lot of sense. It made me wish that dm and md and all their bits and pieces on Linux would fit together as nicely and cleanly as GEOM does.

The panel discussion in the afternoon with Jeff, Bill, and Pawel was also fascinating. It was much less technical than their presentations, and was more focused on some of the challenges they’re facing with ZFS and the directions they see it going in future.

The OpenBSD presentation (originally by Henning Brauer, but he was replaced by UQ’s David Gwynne at the last minute) described some performance improvements that have been made to the OpenBSD network stack and pf (packet filter). I thought David’s presentation style was fun and informative; in spite of knowing nothing about the implementation of OpenBSD, or even of network stacks in general, I still managed to take away a lot of interesting (if OpenBSD-specific) knowledge from the presentation.

The quality of the other three presentations was disappointing. I spent most of their time wishing I’d brought along my EeePC so that I could have got something done instead of just staring at the ceiling.

This was the first time the conference has been run, and it seemed that all-in-all the organizers had done a good job. Things ran more or less smoothly, the QBI building was impressive (if somewhat ostentatious), and even if some of the presentations were a bit lacking, I still found the whole experience highly informative and worthwhile (certainly moreso than JAOO!).

taracallaghan.com

My sister’s photography is now online, under her new domain: taracallaghan.com. My favourite photograph is probably number 10 in The Outsider.

The folding star by Alan Hollinghurst

[star][star][half-star]

Snow crash by Neal Stephenson

[star][star][star][half-star]

Dexter: an omnibus by Jeff Lindsay

[star][half-star]

Miskin Hill launch

The fruit of my labours over the last few months has finally gone live: Miskin Hill Academic Publishing.

That’s the name I picked (more or less at random) for the business I started “to provide electronic publishing services for scholarly journals”. The LaTeX preparation work that I’ve been doing on ASEES for a few years now will be done under the name of Miskin Hill; but more significantly, I’ll also be publishing ASEES online, under the Miskin Hill domain, as an open access journal. Take that, EBSCO!

To begin with, volume 21 (2007) is online now, and I’ll be starting work on the latest issue (2008) very soon. In the meantime I hope to take advantage of a bit of downtime to improve the data feeds published on the site (I already publish metadata in as many formats as I could come up with, but I still haven’t figured out how aggregators like DOAJ and Google Scholar can find new content as it is published). I also have plenty ideas still for the Lucene-based search code. More on that one soon ...

Lolita by Vladimir Nabokov

[star][star][star][star]

The book of tea by Okakura Kakuzo