Instant PropertyChangeSupport
Using a custom MetaClass to add PropertyChangeSupport to any object.
groovy.lang.MissingMethodException: No signature of method MyClass.addPropertyChangeListener() is applicable for argument types ... Argh! Again, an object without bound properties. Now what? Wrap the object in some sort of proxy? Extend the object adding property change support? Neither very appealing. I hate class bloat. And any of the above solutions will create extra classes that will likely be used in one small place. The more classes there are, the more I need to filter out the one off helper classes vs the classes that are the core of the application. Not to mention the new layers of understanding that I don't want to deal if I have to revisit the code at a much later stage. I want an elegant way … a Groovy way! And here is my Groovy way:
myobject.metaClass = new PropertyChangeMetaClass(myobject.metaClass)That is all there is to it! Now I can simply say:
myObject.addPropertyChangeListener(myListener)and so on. Nothing new to learn, nothing new to understand, very very Groovy. My first impression of MetaClass was typical of many Groovy features: 'Why would I need this'? And as usual I now would be pained to live without it. PropertyChangeMetaClass implements the typical PropertyChangeSupport methods. When one adds a property change listener to an object or invokes any setters, PropertyChangeMetaClass intercepts these calls and fires the required messages to any listeners. So on with the Java code:
myObject.addPropertyChangeListener('name', myListner)
import java.beans.PropertyChangeListener;The code is not terribly difficult to understand. The method invokeMethod simply intercepts property change support methods and redirects them to the PropertyChangeSupport instance. And setProperty simply calls PropertyChangeSupport#firePropertyChange after the property has been set. If the special case Expando code is confusing, ignore it. When the new MetaObject implementation is ready, the Expando handling code will be removed. Expando is like a magic bean allowing for dynamically added fields and methods. However its magic confuses the current MetaClass implementation, requiring special handling. That is pretty must all there is to it. I think any Java programmer familiar with PropertyChangeSupport can see what is going on, which makes it a nice and simple example to learn from. Check back soon for how I create correct Swing code without the pains of SwingWorker. It will astound you!
import java.beans.PropertyChangeSupport;
import org.codehaus.groovy.runtime.InvokerHelper;
import groovy.lang.DelegatingMetaClass;
import groovy.lang.MetaClass;
import groovy.lang.MissingPropertyException;
import groovy.util.Expando;
/**
* A delegating meta class that adds property change support to a Groovy object.
*/
public class PropertyChangeMetaClass extends DelegatingMetaClass {
private PropertyChangeSupport support = new PropertyChangeSupport(this);
public PropertyChangeMetaClass(MetaClass metaClass) {
super(metaClass);
}
// --- Property change support methods ---
public void addPropertyChangeListener(PropertyChangeListener l) {
support.addPropertyChangeListener(l);
}
public void addPropertyChangeListener(String propertyName,
PropertyChangeListener l) {
support.addPropertyChangeListener(propertyName, l);
}
public void removePropertyChangeListener(PropertyChangeListener l) {
support.removePropertyChangeListener(l);
}
public void removePropertyChangeListener(String propertyName,
PropertyChangeListener l) {
support.removePropertyChangeListener(propertyName, l);
}
// --- MetaObject overrides ---
@Override
public Object invokeMethod(Object object, String methodName,
Object[] arguments) {
if (methodName.equals("addPropertyChangeListener")) {
if (arguments.length == 1) {
addPropertyChangeListener((PropertyChangeListener) arguments[0]);
} else {
addPropertyChangeListener((String) arguments[0],
(PropertyChangeListener) arguments[1]);
}
return null;
} else if (methodName.equals("removePropertyChangeListener")) {
if (arguments.length == 1) {
removePropertyChangeListener((PropertyChangeListener) arguments[0]);
} else {
removePropertyChangeListener((String) arguments[0],
(PropertyChangeListener) arguments[1]);
}
return null;
}
return super.invokeMethod(object, methodName, arguments);
}
@SuppressWarnings("unchecked")
@Override
public void setProperty(Object object, String property, Object newValue) {
Object oldValue = InvokerHelper.getProperty(object, property);
if ((oldValue == newValue)
|| (oldValue != null && oldValue.equals(newValue)))
return;
try {
super.setProperty(object, property, newValue);
support.firePropertyChange(property, oldValue, newValue);
} catch (MissingPropertyException e) {
// Special case handling for Expando, will go away with new MOP.
if (object instanceof Expando) {
((Expando)object).getProperties().put(property, newValue);
support.firePropertyChange(property, oldValue, newValue);
} else {
throw e;
}
}
}
}
Re: Instant PropertyChangeSupport
Hello Edward!
I like the idea of having bound properties for arbitrary objects simply by modifying its metaclass. However, I tried your solution, but it does not work in all cases.
At first, no event is fired if I set the property using the setter methods:
obj.myProperty = 5 // Fires event
obj.setMyProperty(5) // Doesn't fire event
The reason is that the setter methods don't trigger the setProperty()-method of the metaclass. Maybe that's a bug in my Groovy version, maybe it's a feature, I'm not sure. Instead the invokeMethod()-method is called.
Second issue is, that, when using reflection, the metaclass seems to be ignored at all. I assume that because I used the l2fprod PropertySheet Swing component. This component uses reflection to set an object's properties. But none of the methods in the metaclass, not even invokeMethod(), was triggered.
Have you encountered some of these issues?
Cheers
Benjamin
I like the idea of having bound properties for arbitrary objects simply by modifying its metaclass. However, I tried your solution, but it does not work in all cases.
At first, no event is fired if I set the property using the setter methods:
obj.myProperty = 5 // Fires event
obj.setMyProperty(5) // Doesn't fire event
The reason is that the setter methods don't trigger the setProperty()-method of the metaclass. Maybe that's a bug in my Groovy version, maybe it's a feature, I'm not sure. Instead the invokeMethod()-method is called.
Second issue is, that, when using reflection, the metaclass seems to be ignored at all. I assume that because I used the l2fprod PropertySheet Swing component. This component uses reflection to set an object's properties. But none of the methods in the metaclass, not even invokeMethod(), was triggered.
Have you encountered some of these issues?
Cheers
Benjamin