I've got a simplified Property interface and a mixin that makes the property observable:
public interface Property<B,V> {
V get(B bean);
void set(B bean, V value);
}
public interface ObservableProperty<B,V> extends Property <B,V> {
void addPropertyChangeListener(B bean, PropertyChangeListener p);
void removePropertyChangeListener(B bean, PropertyChangeListener p);
}`</pre>For most use cases, the interface is implemented by a library. For example, this instance uses the getters and setters:
<pre>` ObservableProperty p = new BeanProperty(Customer.class, "address");`</pre>Today I'm interested in the implementation of this interface. Here's a very straightforward implementation that doesn't even need reflection - getting the `Document` property from a `JTextField`:
<pre class="prettyprint">`public class JTextFieldDocumentProperty
implements ObservableProperty<JTextField, Document> {
public Document get(JTextField bean) {
return bean.getDocument();
}
public void set(JTextField bean, Document value) {
bean.setDocument(value);
}
public void addPropertyChangeListener(JTextField bean, PropertyChangeListener p) {
bean.addPropertyChangeListener("document", p);
}
public void removePropertyChangeListener(JTextField bean, PropertyChangeListener p) {
bean.removePropertyChangeListener("document", p);
}
}`</pre>This code is deceivingly simple because the listener types in the Property interface (`PropertyChangeListener`) exactly match the listener types in the bean. Things get a much more interesting if the listener types are different.
**Using an adapter**
The natural solution is to code the `addPropertyChangeListener` to use a simple adapter to convert events of one type to events of another type. This adapter converts between `[DocumentListener](http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/event/DocumentListener.html)` and `[PropertyChangeListener](http://java.sun.com/j2se/1.4.2/docs/api/java/beans/PropertyChangeListener.html)`:
<pre class="prettyprint">`private class DocumentToPropertyChangeListener
implements DocumentListener {
private String currentText;
private final Document document;
private final PropertyChangeListener delegate;
public DocumentToPropertyChangeListener(Document bean, PropertyChangeListener p) {
this.document = bean;
this.delegate = p;
this.currentText = getDocumentText();
}
private String getDocumentText() {
try {
return document.getText(0, document.getLength());
} catch (BadLocationException e) {
throw new RuntimeException(e);
}
}
public void insertUpdate(DocumentEvent e) {
fireChanged();
}
public void removeUpdate(DocumentEvent e) {
fireChanged();
}
public void changedUpdate(DocumentEvent e) {
fireChanged();
}
public void fireChanged() {
String oldText = currentText;
currentText = getDocumentText();
delegate.propertyChange(
new PropertyChangeEvent(document, "text", oldText, currentText));
}
}`</pre>As you can see, the `Document` class is a bit cumbersome to use. But this interface correctly adapts the listener types. Unfortunately, we still have the trickiest problem ahead of us...
**The problem with removePropertyChangeListener()**
In my second example, I'll implement Property for the text of a `[Document](http://java.sun.com/j2se/1.4.2/docs/api/javax/swing/text/Document.html)`. The implementation of `addPropertyChangeListener` is easy; we just create the adapter inline:
<pre class="prettyprint">`public class DocumentTextProperty
implements StatelessProperty<Document, String> {
...
public void addPropertyChangeListener(Document bean, PropertyChangeListener p) {
bean.addDocumentListener(_new DocumentToPropertyChangeListener(bean, p)_);
}
public void removePropertyChangeListener(Document bean, PropertyChangeListener p) {
...
}
}`</pre>But how does remove work? There's a big problem - we didn't save a reference to our adapter! We need to recover that reference somehow so we can remove the same adapter that was added.
The `Document` interface doesn't expose its listeners; ie. there's no `getDocumentListeners()` method. So we can't just iterate over the document's listeners to find the matching adapter.
If we store a reference to the adapter in the `DocumentTextProperty` object, then we'll almost certainly have a memory leak. Even if we stored just a `WeakReference<DocumentTextProperty>`, it would make our Property stateful. In the ideal implementation, any two instances of the same property should be interchangeable. For example, we shouldn't force the user to store a reference to the Property instance. This code should work fine but it won't work if the property is stateful:
<pre class="prettyprint">` public void installListeners() {
new DocumentTextProperty().addPropertyChangeListener(
myDocument, myPropertyChangeListener);
}
...
public void shutdown() {
new DocumentTextProperty().removePropertyChangeListener(
myDocument, myPropertyChangeListener);
}
`</pre>
The next option is to store this state in a static global registry. This is the approach taken by the [bean-properties](https://bean-properties.dev.java.net/) Java.net project. I don't like this approach because it feels very heavyweight:
<li>It needs `WeakReferences`.
</li><li>The shared registry requires synchronization.
</li><li>There's memory overhead to store each registered bean/listener pair.
</li><li>The registry is complex because it requires each listener pair to be uniquely identified.
</li><li>It prevents bean/listener pairs from being serialized.
Fortunately, there is a clever solution to this problem - don't store a reference to the adapter. Instead, when we call `removePropertyChangeListener`, we pass in a different instance that [equals](http://java.sun.com/j2se/1.4.2/docs/api/java/lang/Object.html#equals(java.lang.Object)) the first:
<pre class="prettyprint">`public class DocumentTextProperty
implements StatelessProperty<Document, String> {
...
public void addPropertyChangeListener(Document bean, PropertyChangeListener p) {
bean.addDocumentListener(new DocumentToPropertyChangeListener(bean, p));
}
public void removePropertyChangeListener(Document bean, PropertyChangeListener p) {
bean.removeDocumentListener(_new DocumentToPropertyChangeListener(bean, p)_);
}
}`</pre>This requires us to implement `equals` and `hashCode` in our adapter:
<pre class="prettyprint">`private class DocumentToPropertyChangeListener
implements DocumentListener {
public boolean equals(Object obj) {
return obj != null
&& obj.getClass() == this.getClass()
&& ((DocumentToPropertyChangeListener) obj).delegate.equals(delegate)
&& ((DocumentToPropertyChangeListener) obj).document.equals(document);
}
public int hashCode() {
return delegate.hashCode() * 37 + document.hashCode();
}
}
I love equals()
We've taken advantage of one of Java's greatest features: a strong concept of equality. It makes our solution stateless, immutable, serializable, efficient and easy to use.