« BeansBinding and Properties without strings | Main| Another idea on Property support - using BCEL »

More on Properties without Strings


What I described in my entry on the 17th was after building a proof of concept.  Now I have used this in a real live application, and filled in some of the gaps and also proved that it actually works.

Of course, as has been pointed out, this is only a place holder until Sun (or the OpenJDK community) get around to proper property support in the compiler, and I would drop this like a shot if ever it comes to pass.  

I had tried Annotations, but having to build a separate class, even if it is generated automatically, strikes me as second best.  It is actually also difficult to find the Annotations sometimes, as for instance the PropertyInfo data returned as the class of the property the return type of the getter, not the type (with annotations) of the underlying property.

This code would also be useful not just with BeanBinding (which seems about as moribund as property support), but also with JGoodies bindings, or with the UFacekit work that is being done to make JFace from Eclipse-SWT work with Swing and Qt.  But more on UFacekit later.

The first problem I hit when I used my "real" application, was that I had objects in there that has no default constructor, including the basic numeric types such as Integer.  So I added some special case code to the prepare method thus:-

        public static <T> T prepare(T parent) {
                Field[]fields = parent.getClass().getDeclaredFields();
                for(Field field: fields) {
                        field.setAccessible(true);
                        try {
                                Object value = field.get(parent);
                                if(value == null) {
                                        Class<?>type = field.getType();
                                        if(type.equals(List.class)) value = new ArrayList<Object>();
                                                else if(type.equals(Byte.class)) value = new Byte((byte)0);
                                                else if(type.equals(Short.class)) value = new Short((short)0);
                                                else if(type.equals(Integer.class)) value = new Integer(0);
                                                else if(type.equals(Long.class)) value = new Long(0L);
                                                else if(type.equals(Double.class)) value = new Double(0);
                                                else if(type.equals(Float.class)) value = new Float(0);
                                                else if(type.equals(Boolean.class)) value = new Boolean(false);
                                                else value = field.getType().newInstance();
                                        field.set(parent,value);
                                        }
                        } catch(Exception e) { throw new RuntimeException("preparing object", e); }
                        }
                return parent;
                }

This also catches the problem that many JPA generators use List rather than a specific List as the type of collections.  So as  null entry I just use an ArrayList.

findName remains much as before, with a few extra sanity checks:-

        public static Field findField(Object parent, Object child) {
                if(parent == null) throw new RuntimeException("parent is null");
                if(child == null) throw new RuntimeException("child is null");
                Class<?> c = parent.getClass();
                while(c != null) {
                        Field[]fields = c.getDeclaredFields();
                        for(Field field: fields) {
                                try {
                                        field.setAccessible(true);
                                        if(field.get(parent) == child) return field;
                                } catch(Exception e) {
                                        e.printStackTrace();
                                        }
                                }
                        c = c.getSuperclass();
                        }
                throw new RuntimeException("field not found, parent " + parent + " child " + child);
                }

Notice that is now also checks for fields in superclasses.

I have then introduced a new method, path, which handles compound values.  We will come on to how this is used later, but for now here is the code:-

        public static <T> String path(T parent, Object ... children) {
                StringBuffer path = new StringBuffer();
                Object p = parent;
                for(Object child:children) {
                        Field field = findField(p,child);
                        if(path.length() != 0) path.append('.');
                        path.append(field.getName());
                        p = child;
                        }
                return path.toString();
                }

The property method is then simple:-

        public static <T> Property<T,Object> property(T parent,Object ... children) {
                return BeanProperty.create(path(parent,children));
                }

as it uses the path method to turn the chain of objects into a compound string.

I suppose that possibly as we might need change the name of property to beanProperty, but I need to check what UFacekit uses.

Finally there are a set of methods to get String resources from a ResourceBundle, based on class and field.  A few variants are provided so that a suffix can be added to the name before it is looked up (so for a tab we might want a tab name and a mnemonic, so the suffic would .tab and .mnemonic).  Also as might want to "reparent" a field, so make the grouping of strings in the resource bundle more obvious:-

        public static String resourceWithSuffix(String suffix,JPanel parent,Object ... children) {
                ResourceBundle bundle = ResourceBundle.getBundle("MessageBundle");
                String path = path(parent,children);
                String value = bundle.getString(parent.getClass().getCanonicalName() + "." + path + suffix);
                if(isEmpty(value)) return path;
                return value;
                }
        public static String resource(JPanel parent,Object ... children) {
                return resourceWithSuffix("",parent,children);
                }
        public static String resourceWithParent(JPanel panel,Object parent,Object ... children) {
                ResourceBundle bundle = ResourceBundle.getBundle("MessageBundle");
                String path = path(parent,children);
                String value = bundle.getString(panel.getClass().getCanonicalName() + "." + path);
                if(isEmpty(value)) return path;
                return value;
                }
        public static String resource(JPanel parent,Field field) {
                ResourceBundle bundle = ResourceBundle.getBundle("MessageBundle");
                String path = field.getName();
                String value = bundle.getString(parent.getClass().getCanonicalName() + "." + path);
                if(isEmpty(value)) return path;
                return value;
                }

And there you have it.

In my next blog entries I will show how I use this with beansbinding, and also explore how this might be used with UFaceKit.

Post A Comment

Tag Cloud