« Installing brl-cad on a Debian system | Main| More on Properties without Strings »

BeansBinding and Properties without strings


It bugs me seriously that Java does not have property support.  Instead we are lumbered with coding field names as strings, which can not be checked by IDEs or compilers - back to the days of Scripting languages where the only way to debug it is to run it and watch for unexpected results.

So I went looking for a way to achive those uses of property support that are needed for BeansBinding - which is basically setup time access to field names for class members.  I looked at Annotations, and at the Bean-Properties (https://bean-properties.dev.java.net), but none did what I wanted.  Bean-Properties is really interesting, and there are only two problems with it, firstly development seems to have stopped, and secondly it does not integrate cleanly with JPA and I want to use JPA.

Java does not provide a means to express a field as anything other than a value, and every attempt to add property support to Java seems to get rubished as unnecessary, and as a polution of the language.

The basic problem is that getting back to the declaration of a value from its value is not something that Java makes easy.  Well actually stating it that way gives the clue as to how to proceed.

Now Java passes values around by reference, not by value - yes I know this is a simplification but it will do for here.  Java also has a literal pool, so constants will be references to that pool, not to unique  values.  This literal pool is used for things like String literals, and uses the fact that String values are immutable to enable this optimisation.

As I am working with BeanBinding and JPA it also makes sense to have a restriction that no native types are allowed, they must all be wrapped.  So int becomes Integer etc.

So this leads to a solution, which works for me.  Lets start with forms, i.e taking a single class object and displaying/editing fields within it.

I tend to have a single object of the relevant class, and I bind that object to the fields - or rather I bind the members of that object to the display fields.  I do this so that I can then have save and cancel buttons, and do proper logical validations before I copy the class object back to wherever it is needed.  This also means that I only do binding once, and I only do it during intialisation.

Getting from Class object (parent) to field is easy using getDeclaredFields.

As a reference is passed around we can find which field this value came from using the == operator, provided all the values are unique.  So by finding the field which contains the same value as the one we are looking for we have found our field.  So here is some static methods to find a field from an object, to find a property from an object, and find a resource (string to display) for a field in a class describing the UI.

        public static Field findField(Object parent, Object child) {
                Field[]fields = parent.getClass().getDeclaredFields();
                for(Field field: fields) {
                        try {
                                field.setAccessible(true);
                                if(field.get(parent) == child) return field;
                        } catch(Exception e) {
                                e.printStackTrace();
                                }
                        }
                return null;
                }
        public static <T> Property<T,Object> property(T parent,Object child) {
                Field field = findField(parent,child);
                if(field == null) return null;
                return BeanProperty.create(field.getName());
                }
        public static String resource(JPanel parent,String child) {
                ResourceBundle bundle = ResourceBundle.getBundle("MessageBundle");
                String value = bundle.getString(parent.getClass().getCanonicalName() + "." + child);
                if(isEmpty(value)) return child;
                return value;
                }

Now of course all of this relies on the fields all containing unique values.  You can of course do this yourself, but here is another static method which would do what is needed.  It does assume that all objects that might be in the system can be instantiated:-

        public static <T> T prepare(T parent) {
                HashMap<Object,String>map = new HashMap<Object,String>();
                Field[]fields = parent.getClass().getDeclaredFields();
                for(Field field: fields) {
                        field.setAccessible(true);
                        try {
                                Object value = field.get(parent);
                                if(value == null) {
                                        value = field.getType().newInstance();
                                        field.set(parent,value);
                                        }
                                String oldField = map.get(value);
                                if(oldField != null) throw new IllegalArgumentException( "value not unique, " +
                                                   field.getName() + " has the same value as " + oldField);
                                map.put(value, field.getName());
                        } catch(Exception e) { throw new RuntimeException("preparing object", e); }
                        }
                return parent;
                }

The glorious thing is that it works.  All that remains is a mechanism to cope with compound names.

To use this we take a class with fields we want to bind, create an instance, prepare it, and bind it to Swing components:-

   public class Contact {
      private String name;
      private String phone;
      private String email
      public String getName() { return name; }
      public void setName(String newValue) { name = newValue; }
      public String getPhone() { return phone; }
      public void setPhone(String newValue) { phone = newValue; }
      public String getEmail() { return email; }
      public void setEmail(String newValue) { email = newValue; }
      }

   public class Panel extends JPanel {
      JTextField name = new JTextField();
      JTextField phone = new JTextField();
      JTextField email = new JTextField();
      private static final Property textProp = BeanProperty.create("text");
      public void bind(Contact contact) {
         Property prop = property(contact,contact.getName());
         Bindings.createAutoBinding(UpdateStrategy.READ_WRITE, contact, prop, name, textProp).bind();
         Property prop = property(contact,contact.getPhone());
         Bindings.createAutoBinding(UpdateStrategy.READ_WRITE, contact, prop, phone, textProp).bind();
         Property prop = property(contact,contact.getEmail());
         Bindings.createAutoBinding(UpdateStrategy.READ_WRITE, contact, prop, email, textProp).bind();
               }
          }  

Look, NO Strings (apart from the text one for the JTextField)!

Please note, all code shown in this blog entry are Copyright (C) David Goodenough & Associates Limited 2008, and are licenced as LGPLV2 (please see http://fsf.org for a full copy of this licence)

Comments

GravatarImage1 - I've been doing this for some time in conjunction with the 'SeparatedTableModel' idea from: { Link }

I bind each row in a JTable to a bean instance, and then setup columns that use reflection upon that bean's fields.

For any issues of duplicate classes in my bean, I simply made a 2-level deep compound search strategy, where I could specify two classes, and most complex tables worked fine with that level of depth in searching with reflection. I left in the String field option too.

I'm now exploring other ways, with String hints, or potentially even relying on order of declared fields.

GravatarImage2 - I used the reverse approach; I have a reverse engineered business model with a generated layer of entity classes. Aside from the setter and getters I also generate static strings that hold the name of the property. By using these constants, I make sure that my code is compiler checkable (I would not have it any other way).
Now I already used JGoodies binding, but the problems are the same. My binding code looks like:

BindingUtil.bind(iBeanAdapter.getValueModel(Relation.NAME_PROPERTY_ID), lRelationNameJTextfield);

Another advantage of this approach is that you can also use this in configuration. I'm also a sucker for programmatic configuration (again compiler checking) and one of my configuration settings look like:

JpaSearchBuilder.setOnlyShow(Relation.class, Relation.RELATIONNR_PROPERTY_ID, Relation.NAME_PROPERTY_ID);

This line tells my generic JPA query builder that, when displaying the results for a query that finds Relation objects, to only show two columns. Again, changes to the BM are compiler detactable.

GravatarImage3 - So you didn't use Bean-Properties because it isn't being developed anymore.. and you think beansbidnings is? hasn't it been supplanted by JavaFX binding as provided by the language.

I always thought the use of strings in beansbindings was merely a sort-term fix before properties were added to the Java language.. of course that JSR hasn't gotten anywhere either.

GravatarImage4 - This is not a bad idea but I feel the appropriate way to do this is using annotations. Would it be so difficult to have a @BeanBindingProperty annotation on domain object properties and then have another object, a BeanBindingAnnotationProcessor, that extracted said properties and then used the Bindings calls?

(Then again I don't use the BeanBinding API and don't think it's such a great idea.)

GravatarImage5 - ocean - do you mean annotations like ClassBuilder? { Link }

That's much what I'd hoped for with the language properties support pencilled in for JDK7 and full support with beansbindings (also pencilled in for JDK7).. seems unlikely JDK7 will see either now as they've both been virtually abandoned for a year or more (or not a current priority as Sun call it).

Post A Comment

Tag Cloud