Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JavaBeans and introspection - messed up on boolean and indexed properties?

A former colleague of mine started a discussion half an hour ago about JavaBeans, and why they didn't quite work the way he wants in JSF. The particular case is about boolean properties.

1. For a boolean property named isUrl Eclipse generates this

private boolean isUrl;
public boolean isUrl() {..}
public boolean setUrl(boolean url) {..}

But this does not work in JSF. He made it work by adding public boolean getIsUrl() The implementation might be buggy, so let's make sure who's right, by using the introspection API.:

BeanInfo info = Introspector.getBeanInfo(ClassTest.class);
for (PropertyDescriptor pd : info.getPropertyDescriptors()) {
      System.out.println(pd.getName() + ": " + pd.getReadMethod() + 
       " : " + pd.getWriteMethod());
}

For the above code, this prints both methods - i.e. Eclipse is right, JSF is wrong. But that sounded suspicious to me, since the specification doesn't mention anything about the double "is".

But while looking through the spec, I saw something I've never used - the so called indexed properties. You can have private String[] bar and then public String getBar(int idx). So:

2. I tried that with the Introspector, and it didn't find a read method for bar. The result from the above code was: bar: null : null. So I came to think - now the introspector does not follow the spec. Perhaps it didn't follow it in the previous case, and ultimately, JSF is right. In fact, indexed properties can make so that there are two read methods for a given property. And that's not possible with the PropertyDescriptor class of the introspection API.

What does this lead us to - we have a possibly broken API that does not conform to the spec. Which leads to other implementations of the spec (JSF uses a custom one obviously). Which leads to further misunderstandings and confusions.

A sidenote for something that bothered me - in the JavaBeans spec they call the naming conventions for the methods "design patterns". This sounds wrong to me.

So, now onto the questions:

  1. is the JavaBeans spec clear
  2. is the introspection API correct
  3. is a new JavaBeans specification needed, at least to clarify the behaviour of booleans (that's subjective to an extent)

Update. it appears the JSF usage is bean.isUrl rathern than bean.url. Which makes perfects sense not to work with isUrl() accessor.

P.S. JDK 1.6.0_20, JSF 1.2, MyFaces

like image 680
Bozho Avatar asked Jan 16 '11 22:01

Bozho


1 Answers

Java Bean Properties are defined by methods, not by fields. For this reason the PropertyDescriptor class has getReadMethod() and getWriteMethod() methods, but no getField() methods.

Personally, I think your colleague is using a bad practice.

a) is is a verb. Fields should not be named after verbs.
b) while it's not required, good practice is to name the field like the property, which lets you write code like this:

PropertyDescriptor pd; // let's assume this is set
Method referenceMethod = pd.getReadMethod() == null
  // at least one of these is not null
  ? pd.getWriteMethod() : pd.getReadMethod(); 
Field underLyingField = referenceMethod
                          .getDeclaringClass()
                          .getDeclaredField(pd.getName());

While this code is not standardized, it is following conventions and can come in very handy. If you don't follow conventions like this, you have no way of associating a field with a property (which is intentional, I know).

e.g. I use code like the above to check if the field has annotations


About indexed properties:

You can use the index syntax on array or list (or map) properties, but only if they are defined as standard bean properties.

So if you have a property like this:

private String[] bar;
public String[] getBar(){
    return bar;
}
public void setBar(String[] bar){
    this.bar = bar;
}

or like this:

private List<String> bar;
public List<String> getBar(){
    return bar;
}
public void setBar(List<String> bar){
    this.bar = bar;
}

you can access the first member with the expression ${bar[0]}

And with a map property like this:

private Map<String, String> bar;
public Map<String, String> getBar(){
    return bar;
}
public void setBar(Map<String, String> bar){
    this.bar = bar;
}

You can access the value mapped to "baz" like this ${bar['baz']}.

This functionality builds on top of standard beans functionality, so it requires regular getters / setters.

like image 89
Sean Patrick Floyd Avatar answered Nov 08 '22 10:11

Sean Patrick Floyd