Freitag, 22. Juli 2011

Java properties

Today, I once again complaint about the missing of first-class properties in java. A long term wish a lot of people would like to see fixed. Beside first-class methods (really?) and Closures for sure.

Today, I once again searched the internet about the current state of this wish.
And, once again, I saw that nothing happened ... really nothing?

No, I came across the term "abstract enum" and how one would like to represent Java properties with this extension to java. [1]
I were excited immediately, just to see, that an "abstract enum" is not possible with Java yet, and that this is also a long term wish someone would like to see.

*rant-on* Seems like Java is a big hive of "long-term-wishes" *rant-off*

Nevertheless, I wanted to see how it feels to have properties like that.

I started with two simple classes
public class PropBean
{
    public static enum p
    {
        propA, propB, propC, propD
    }

    private String propA = "pA";

    private String propB = "pB";

    private String propC = "pC";

    private String propD = "pD";

    public String getPropA()
    {
        return "f" + propA;
    }

    public void setPropA(String propA)
    {
        this.propA = propA;
    }

    public String getPropB()
    {
        return "f" + propB;
    }

    public void setPropB(String propB)
    {
        this.propB = propB;
    }
}

public class PropBean2 extends PropBean
{
    private String propD = "YY";

    public String getPropA()
    {
        return "XX";
    }
}

Not much magic, but as you can see, the PropBean class declares an enum (p) with all the fields we would like to expose as (kind of) "first-class" properties.

Now, with a modified (compileable with the current Java version) PropertyDefinition class (as outlined in the blog [1]) I am able to do the following:

public static void main(String[] args)
    {
        PropBean2 bean = new PropBean2();

        System.err.println(property(PropBean2.p.propA).get(bean));
        System.err.println(property(PropBean2.p.propB).get(bean));
        System.err.println(property(PropBean2.p.propC).get(bean));
        System.err.println(property(PropBean2.p.propD).get(bean));
    }

and the output will be

XX
fpB
pC
YY

Now, THAT is not that bad I think. Good enough for a "poor-man's-property". Sure the enumeration is not combile-time-safe against refactorings, but yay, this is something I can not fix. Probably one can create an IDE plugin (IntelliJ IDEA please!) to automagically populate this enum!?

And, sure, this thing is not able to deal with PropertyChangeSupport, for this I use AspectJ already - curious? ;-)


Here we go with the completely undocumented PropertyDefinition class - just in case one would like to start experimenting too.

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

public class PropertyDefinition
{
    private final static Map<Enum, PropertyDefinition> propertyDefinitions = new ConcurrentHashMap<Enum, PropertyDefinition>();

    private final Map<Class, PropertyDefinitionAccessor> accessors = new ConcurrentHashMap<Class, PropertyDefinitionAccessor>();

    private final Class modelClass;

    private final String fieldName;
    private final String propName;

    private PropertyDefinition(Enum en)
    {
        modelClass = en.getDeclaringClass().getEnclosingClass();
        fieldName = en.name();
        propName = Character.toUpperCase(fieldName.charAt(0)) + fieldName.substring(1);
    }

    public Object get(Object obj)
    {
        PropertyDefinitionAccessor accessor = getAccessor(obj);

        return accessor.get(obj);
    }

    private PropertyDefinitionAccessor getAccessor(Object obj)
    {
        Class clazz = obj.getClass();
        PropertyDefinitionAccessor accessor = accessors.get(clazz);
        if (accessor == null)
        {
            accessor = new PropertyDefinitionAccessor(clazz);
            accessors.put(clazz, accessor);
        }
        return accessor;
    }

    public void set(Object obj, Object value)
    {
        PropertyDefinitionAccessor accessor = getAccessor(obj);

        accessor.set(obj, value);
    }

    private class PropertyDefinitionAccessor
    {
        private final Class modelClass;
        private final String getterMethodName;
        private final String setterMethodName;
        private Field reflectField;
        private Method getterMethod;
        private Method setterMethod;

        public PropertyDefinitionAccessor(Class clazz)
        {
            this.modelClass = clazz;

            this.getterMethodName = "get" + propName;
            this.setterMethodName = "set" + propName;

            try
            {
                this.reflectField = modelClass.getDeclaredField(fieldName);
                this.reflectField.setAccessible(true);
            }
            catch (NoSuchFieldException e)
            {
                try
                {
                    this.reflectField = PropertyDefinition.this.modelClass.getDeclaredField(fieldName);
                    this.reflectField.setAccessible(true);
                }
                catch (NoSuchFieldException e2)
                {
                    throw new RuntimeException(e);
                }
            }

            try
            {
                this.getterMethod = modelClass.getDeclaredMethod(this.getterMethodName);
            }
            catch (NoSuchMethodException e)
            {
                try
                {
                    this.getterMethod = PropertyDefinition.this.modelClass.getDeclaredMethod(this.getterMethodName);
                }
                catch (NoSuchMethodException e2)
                {
                    // nothing overloaded
                }
            }

            try
            {
                this.setterMethod = modelClass.getDeclaredMethod(this.setterMethodName, this.reflectField.getType());
            }
            catch (NoSuchMethodException e)
            {
                try
                {
                    this.setterMethod = PropertyDefinition.this.modelClass.getDeclaredMethod(this.setterMethodName, this.reflectField.getType());
                }
                catch (NoSuchMethodException e2)
                {
                    // nothing overloaded
                }
            }
        }

        // Here there is a need to type this
        public Object get(Object obj)
        {
            try
            {
                if (getterMethod != null)
                {
                    return getterMethod.invoke(obj);
                }

                return reflectField.get(obj);
            }
            catch (Throwable e)
            {
                throw new RuntimeException(e);
            }
        }

        public void set(Object obj, Object value)
        {
            try
            {
                if (setterMethod != null)
                {
                    setterMethod.invoke(obj, value);
                    return;
                }

                reflectField.set(obj, value);
            }
            catch (Throwable e)
            {
                throw new RuntimeException(e);
            }
        }
    }

    public static PropertyDefinition property(Enum e)
    {
        PropertyDefinition def = propertyDefinitions.get(e);
        if (def == null)
        {
            def = new PropertyDefinition(e);
            propertyDefinitions.put(e, def);
        }

        return def;
    }
}

[1] [http://freddy33.blogspot.com/2007/05/why-java-enum-cannot-extends.html]