B
- the type of the bean managed by this BeanAdapterpublic class BeanAdapter<B> extends Model
ValueModels can be created for a property name using
getValueModel(String)
or for a triple of (property name, getter
name, setter name) using getValueModel(String, String, String)
.
If you just specify the property name, the adapter uses the standard
Java Bean introspection to lookup the available properties and how to
read and write the property value. In case of custom readers and writers
you may specify a custom BeanInfo class, or as a shortcut use the method
that accepts the optional getter and setter name. If these are specified,
introspection will be bypassed and a PropertyDescriptor will be
created for the given property name, getter and setter name.
Note: For each property name subsequent calls
to these methods must use the same getter and setter names. Attempts
to violate this constraint are rejected with an IllegalArgumentException.
Property values for a given property name can be read using
getValue(String)
. To set a value for a for a property name
invoke setValue(String, Object)
.
Optionally the BeanAdapter can observe changes in bound
properties as described in section 7.4 of the Bean specification.
The bean then must provide support for listening on properties as described
in section 7.4 of this specification.
You can enable this feature by setting the constructor parameter
observeChanges
to true
.
If the adapter observes changes, the ValueModels returned by
#getValueModel
will fire value change events,
i.e. PropertyChangeEvents for the property "value"
.
Even if you ignore property changes, you can access the adapted
property value via #getValue()
.
It's just that you won't be notified about changes.
In addition you can observe the bean's bound properties
by registering PropertyChangeListeners with the bean using
#addBeanPropertyChangeListener
. These listeners will be removed
from the old bean before the bean changes and will be re-added after
the new bean has been set. Therefore these listeners will be notified
about changes only if the current bean changes a property. They won't be
notified if the bean changes - and in turn the property value. If you want
to observes property changes caused by bean changes too, register with the
adapting ValueModel as returned by #getValueModel(String)
.
The BeanAdapter provides two access styles to the target bean
that holds the adapted property: you can specify a bean directly,
or you can use a bean channel to access the bean indirectly.
In the latter case you specify a ValueModel
that holds the bean that in turn holds the adapted properties.
If the adapted bean is null
the BeanAdapter can
neither read nor set a value. In this case #getValue
returns null
and #setValue
will silently
ignore the new value.
This adapter throws three PropertyChangeEvents if the bean changes:
beforeBean, bean and afterBean. This is useful
when sharing a bean channel and you must perform an operation before
or after other listeners handle a bean change. Since you cannot rely
on the order listeners will be notified, only the beforeBean
and afterBean events are guaranteed to be fired before and
after the bean change is fired.
Note that #getBean()
returns the new bean before
any of these three PropertyChangeEvents is fired. Therefore listeners
that handle these events must use the event's old and new value
to determine the old and new bean.
The order of events fired during a bean change is:
Note:
BeanAdapters that observe changes have a PropertyChangeListener
registered with the target bean. Hence, a bean has a reference
to any BeanAdapter that observes it. To avoid memory leaks
it is recommended to remove this listener if the bean lives much longer
than the BeanAdapter, enabling the garbage collector to remove the adapter.
To do so, you can call setBean(null)
or set the
bean channel's value to null.
As an alternative you can use event listener lists in your beans
that implement references with WeakReference
.
Setting the bean to null has side-effects, for example the adapter
fires a change event for the bound property bean and other properties.
And the value of ValueModel's vended by this adapter may change.
However, typically this is fine and setting the bean to null
is the first choice for removing the reference from the bean to the adapter.
Another way to clear the reference from the target bean is
to call #release
. It has no side-effects, but the adapter
must not be used anymore once #release has been called.
Constraints: If property changes shall be observed, the bean class must support bound properties, i. e. it must provide the following pair of methods for registration of multicast property change event listeners:
public void addPropertyChangeListener(PropertyChangeListener x); public void removePropertyChangeListener(PropertyChangeListener x);PropertyAdapter vs. BeanAdapter vs. PresentationModel
PropertyAdapter
does for a
single bean property.
If you adapt multiple properties of the same bean, you better use
the BeanAdapter. It registers a single PropertyChangeListener with the bean,
where multiple PropertyAdapters would register multiple listeners.
If you adapt bean properties for an editor, you will typically use the
PresentationModel
. The PresentationModel is
more powerful than the BeanAdapter. It adds support for buffered models,
and provides an extensible mechanism for observing the change state
of the bean and related objects.Basic Examples:
// Direct access, ignores changes Address address = new Address() BeanAdapter adapter = new BeanAdapter(address); adapter.setValue("street", "Broadway"); System.out.println(address.getStreet()); // Prints "Broadway" address.setStreet("Franz-Josef-Str."); System.out.println(adapter.getValue("street")); // Prints "Franz-Josef-Str." //Direct access, observes changes BeanAdapter adapter = new BeanAdapter(address, true); // Indirect access, ignores changes ValueHolder addressHolder = new ValueHolder(address1); BeanAdapter adapter = new BeanAdapter(addressHolder); adapter.setValue("street", "Broadway"); // Sets the street in address1 System.out.println(address1.getStreet()); // Prints "Broadway" adapter.setBean(address2); adapter.setValue("street", "Robert-Koch-Str."); // Sets the street in address2 System.out.println(address2.getStreet()); // Prints "Robert-Koch-Str." // Indirect access, observes changes ValueHolder addressHolder = new ValueHolder(); BeanAdapter adapter = new BeanAdapter(addressHolder, true); addressHolder.setValue(address1); address1.setStreet("Broadway"); System.out.println(adapter.getValue("street")); // Prints "Broadway" // Access through ValueModels Address address = new Address(); BeanAdapter adapter = new BeanAdapter(address); ValueModel streetModel = adapter.getValueModel("street"); ValueModel cityModel = adapter.getValueModel("city"); streetModel.setValue("Broadway"); System.out.println(address.getStreet()); // Prints "Broadway" address.setCity("Hamburg"); System.out.println(cityModel.getValue()); // Prints "Hamburg"Adapter Chain Example:
Country country = new Country(); country.setName("Germany"); country.setEuMember(true); BeanAdapter countryAdapter = new BeanAdapter(country, true); JTextField nameField = new JTextField(); nameField.setDocument(new DocumentAdapter( countryAdapter.getValueModel("name"))); JCheckBox euMemberBox = new JCheckBox("Is EU Member"); euMemberBox.setModel(new ToggleButtonAdapter( countryAdapter.getValueModel("euMember"))); // Using factory methods JTextField nameField = Factory.createTextField(country, "name"); JCheckBox euMemberBox = Factory.createCheckBox (country, "euMember"); euMemberBox.setText("Is EU Member");
As of version 1.3 this class is no longer marked as final, but lacks the documentation for subclass constraints. I plan to introduce an interface that shall describe the semantics required by the PresentationModel class. Until then, this BeanAdapter implementation describes the semantics and all constraints.
TODO: Improve the class comment and focus on the main features.
TODO: Consider adding a feature to ensure that update notifications are performed in the event dispatch thread. In case the adapted bean is changed in a thread other than the event dispatch thread, such a feature would help complying with Swing's single thread rule. The feature could be implemented by an extended PropertyChangeSupport.
TODO: I plan to improve the support for adapting beans that do not fire PropertyChangeEvents. This affects the classes PropertyAdapter, BeanAdapter, and PresentationModel. Basically the PropertyAdapter and the BeanAdapter's internal SimplePropertyAdapter's shall be able to optionally self-fire a PropertyChangeEvent in case the bean does not. There are several downsides with self-firing events compared to bound bean properties. See Issue 49 for more information about the downsides.
The observeChanges constructor parameter shall be replaced by a more fine-grained choice to not observe (former observeChanges=false), to observe bound properties (former observeChanges=true), and a new setting for self-firing PropertyChangeEvents if a value is set. The latter case may be further splitted up to specify how the self-fired PropertyChangeEvent is created:
PropertyAdapter
,
ValueModel
,
ValueModel.getValue()
,
ValueModel.setValue(Object)
,
PropertyChangeEvent
,
PropertyChangeListener
,
Introspector
,
BeanInfo
,
PropertyDescriptor
Modifier and Type | Class and Description |
---|---|
class |
BeanAdapter.SimplePropertyAdapter
Implements the access to the individual bean properties.
|
Modifier and Type | Field and Description |
---|---|
static java.lang.String |
PROPERTYNAME_AFTER_BEAN
The property name used in the PropertyChangeEvent that is fired
after the bean property fires its PropertyChangeEvent.
|
static java.lang.String |
PROPERTYNAME_BEAN
The name of the read-write bound property that holds the target bean.
|
static java.lang.String |
PROPERTYNAME_BEFORE_BEAN
The property name used in the PropertyChangeEvent that is fired
before the bean property fires its PropertyChangeEvent.
|
static java.lang.String |
PROPERTYNAME_CHANGED
The name of the read-only bound bean property that
indicates whether one of the observed properties has changed.
|
Constructor and Description |
---|
BeanAdapter(B bean)
Constructs a BeanAdapter for the given bean;
does not observe changes.
|
BeanAdapter(B bean,
boolean observeChanges)
Constructs a BeanAdapter for the given bean;
observes changes if specified.
|
BeanAdapter(ValueModel beanChannel)
Constructs a BeanAdapter for the given bean channel;
does not observe changes.
|
BeanAdapter(ValueModel beanChannel,
boolean observeChanges)
Constructs a BeanAdapter for the given bean channel;
observes changes if specified.
|
Modifier and Type | Method and Description |
---|---|
void |
addBeanPropertyChangeListener(java.beans.PropertyChangeListener listener)
Adds a PropertyChangeListener to the list of bean listeners.
|
void |
addBeanPropertyChangeListener(java.lang.String propertyName,
java.beans.PropertyChangeListener listener)
Adds a PropertyChangeListener to the list of bean listeners for a
specific property.
|
protected BeanAdapter.SimplePropertyAdapter |
createPropertyAdapter(java.lang.String propertyName,
java.lang.String getterName,
java.lang.String setterName)
Creates and returns a SimplePropertyAdapter that adapts
the bound property with the specified name.
|
B |
getBean()
Returns the Java Bean that holds the adapted properties.
|
ValueModel |
getBeanChannel()
Returns the ValueModel that holds the bean that in turn holds
the adapted properties.
|
java.beans.PropertyChangeListener[] |
getBeanPropertyChangeListeners()
Returns an array of all the property change listeners
registered on this component.
|
java.beans.PropertyChangeListener[] |
getBeanPropertyChangeListeners(java.lang.String propertyName)
Returns an array of all the listeners which have been associated
with the named property.
|
boolean |
getObserveChanges()
Answers whether this adapter observes changes in the
adapted Bean properties.
|
java.lang.Object |
getValue(java.lang.String propertyName)
Returns the value of specified bean property,
null
if the current bean is null . |
BeanAdapter.SimplePropertyAdapter |
getValueModel(java.lang.String propertyName)
Looks up and lazily creates a ValueModel that adapts
the bound property with the specified name.
|
BeanAdapter.SimplePropertyAdapter |
getValueModel(java.lang.String propertyName,
java.lang.String getterName,
java.lang.String setterName)
Looks up and lazily creates a ValueModel that adapts the bound property
with the specified name.
|
boolean |
isChanged()
Answers whether a bean property has changed since the changed state
has been reset.
|
void |
release()
Removes the PropertyChangeHandler from the observed bean, if the bean
is not
null and if bean property changes are observed. |
void |
removeBeanPropertyChangeListener(java.beans.PropertyChangeListener listener)
Removes a PropertyChangeListener from the list of bean listeners.
|
void |
removeBeanPropertyChangeListener(java.lang.String propertyName,
java.beans.PropertyChangeListener listener)
Removes a PropertyChangeListener from the listener list for a specific
property.
|
void |
resetChanged()
Resets this tracker's changed state to
false . |
void |
setBean(B newBean)
Sets a new Java Bean as holder of the adapted properties.
|
void |
setValue(java.lang.String propertyName,
java.lang.Object newValue)
Sets the given new value for the specified bean property.
|
void |
setVetoableValue(java.lang.String propertyName,
java.lang.Object newValue)
Sets a new value for the specified bean property.
|
createPropertyChangeSupport, firePropertyChange
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
addPropertyChangeListener, removePropertyChangeListener
public static final java.lang.String PROPERTYNAME_BEFORE_BEAN
public static final java.lang.String PROPERTYNAME_BEAN
getBean()
,
setBean(Object)
,
Constant Field Valuespublic static final java.lang.String PROPERTYNAME_AFTER_BEAN
public static final java.lang.String PROPERTYNAME_CHANGED
isChanged()
,
Constant Field Valuespublic BeanAdapter(B bean)
Installs a default bean channel that checks the identity not equity to ensure that listeners are reregistered properly if the old and new bean are equal but not the same.
bean
- the bean that owns the properties to adaptpublic BeanAdapter(B bean, boolean observeChanges)
Installs a default bean channel that checks the identity not equity to ensure that listeners are reregistered properly if the old and new bean are equal but not the same.
bean
- the bean that owns the properties to adaptobserveChanges
- true
to observe changes of bound
or constrained properties, false
to ignore changesPropertyUnboundException
- if observeChanges
is true but the property is unbound, i. e. the bean
does not provide a pair of methods to register a multicast
PropertyChangeListenerpublic BeanAdapter(ValueModel beanChannel)
It is strongly recommended that the bean channel checks the identity not equity. This ensures that listeners are reregistered properly if the old and new bean are equal but not the same.
beanChannel
- the ValueModel that holds the beanpublic BeanAdapter(ValueModel beanChannel, boolean observeChanges)
It is strongly recommended that the bean channel checks the identity not equity. This ensures that listeners are reregistered properly if the old and new bean are equal but not the same.
beanChannel
- the ValueModel that holds the beanobserveChanges
- true
to observe changes of bound
or constrained properties, false
to ignore changesjava.lang.IllegalArgumentException
- if the beanChannel is a ValueHolder
that has the identityCheck feature disabledPropertyUnboundException
- if observeChanges
is true but the property is unbound, i. e. the bean
does not provide a pair of methods to register a multicast
PropertyChangeListenerpublic ValueModel getBeanChannel()
#getValueModel
.getBean()
,
setBean(Object)
public B getBean()
setBean(Object)
public void setBean(B newBean)
#getValueModel
.
Also notifies listeners that have been registered with this adapter
to observe the bound property bean.
Resets the changed state to false
.
If this adapter observes bean changes, the bean change handler
will be removed from the former bean and will be added to the new bean.
Hence, if the new bean is null
, this adapter has no
listener registered with a bean.
And so, setBean(null)
can be used as a clean release method
that allows to use this adapter later again.
newBean
- the new holder of the adapted propertiesgetBean()
,
isChanged()
,
resetChanged()
,
release()
public boolean getObserveChanges()
public java.lang.Object getValue(java.lang.String propertyName)
null
if the current bean is null
.This operation is supported only for readable bean properties.
propertyName
- the name of the property to be readjava.lang.NullPointerException
- if propertyName is nulljava.lang.UnsupportedOperationException
- if the property is write-onlyPropertyNotFoundException
- if the property could not be foundPropertyAccessException
- if the value could not be readpublic void setValue(java.lang.String propertyName, java.lang.Object newValue)
null
. If the setter associated
with the propertyName throws a PropertyVetoException, it is silently
ignored.Notifies the associated value change listeners if the bean reports a property change. Note that a bean may suppress PropertyChangeEvents if the old and new value are the same, or if the old and new value are equal.
This operation is supported only for writable bean properties.
propertyName
- the name of the property to setnewValue
- the value to setjava.lang.NullPointerException
- if propertyName is nulljava.lang.UnsupportedOperationException
- if the property is read-onlyPropertyNotFoundException
- if the property could not be foundPropertyAccessException
- if the new value could not be setpublic void setVetoableValue(java.lang.String propertyName, java.lang.Object newValue) throws java.beans.PropertyVetoException
null
. If the setter associated with the propertyName
throws a PropertyVetoException, this methods throws the same exception.Notifies the associated value change listeners if the bean reports a property change. Note that a bean may suppress PropertyChangeEvents if the old and new value are the same, or if the old and new value are equal.
This operation is supported only for writable bean properties.
propertyName
- the name of the property to setnewValue
- the value to setjava.lang.NullPointerException
- if propertyName is nulljava.lang.UnsupportedOperationException
- if the property is read-onlyPropertyNotFoundException
- if the property could not be foundPropertyAccessException
- if the new value could not be setjava.beans.PropertyVetoException
- if the bean setter
throws a PropertyVetoExceptionpublic BeanAdapter.SimplePropertyAdapter getValueModel(java.lang.String propertyName)
Subsequent calls to this method with the same property name return the same ValueModel.
To prevent potential runtime errors this method eagerly looks up the associated PropertyDescriptor if the target bean is not null.
For each property name all calls to this method and to
#getValueModel(String, String, String)
must use
the same getter and setter names. Attempts to violate this constraint
will be rejected with an IllegalArgumentException. Especially once
you've called this method you must not call
#getValueModel(String, String, String)
with a non-null
getter or setter name. And vice versa, once you've called the latter
method with a non-null getter or setter name, you must not call
this method.
This method uses a return type of AbstractValueModel, not a ValueModel.
This makes setVetoableValue(String, Object)
visible. It also
makes the AbstractValueModel convenience type converters available,
which can significantly shrink the source code necessary to read and
write values from/to these models.
propertyName
- the name of the property to adaptjava.lang.NullPointerException
- if propertyName is nullPropertyNotFoundException
- if the property could not be foundjava.lang.IllegalArgumentException
- if #getValueModel(String, String, String)
has been
called before with the same property name and a non-null getter
or setter namepublic BeanAdapter.SimplePropertyAdapter getValueModel(java.lang.String propertyName, java.lang.String getterName, java.lang.String setterName)
#getValueModel(String)
this method bypasses the Bean Introspection and uses the given getter
and setter names to setup the access to the adapted Bean property.Subsequent calls to this method with the same parameters will return the same ValueModel.
To prevent potential runtime errors this method eagerly looks up the associated PropertyDescriptor if the target bean is not null.
For each property name all calls to this method
and to #getValueModel(String)
must use the same
getter and setter names. Attempts to violate this constraint
will be rejected with an IllegalArgumentException. Especially
once you've called this method with a non-null getter or setter name,
you must not call #getValueModel(String)
. And vice versa,
once you've called the latter method you must not call this method
with a non-null getter or setter name.
This method uses a return type of AbstractValueModel, not a ValueModel.
This makes setVetoableValue(String, Object)
visible. It also
makes the AbstractValueModel convenience type converters available,
which can significantly shrink the source code necessary to read and
write values from/to these models.
propertyName
- the name of the property to adaptgetterName
- the name of the method that reads the valuesetterName
- the name of the method that sets the valuejava.lang.NullPointerException
- if propertyName is nullPropertyNotFoundException
- if the property could not be foundjava.lang.IllegalArgumentException
- if this method has been called before
with the same property name and different getter or setter namesprotected BeanAdapter.SimplePropertyAdapter createPropertyAdapter(java.lang.String propertyName, java.lang.String getterName, java.lang.String setterName)
propertyName
- the name of the property to adaptgetterName
- the name of the method that reads the valuesetterName
- the name of the method that sets the valuepublic boolean isChanged()
public void resetChanged()
false
.public void addBeanPropertyChangeListener(java.beans.PropertyChangeListener listener)
The listener will be notified if and only if this BeanAdapter's current bean changes a property. It'll not be notified if the bean changes.
If listener is null
, no exception is thrown and no action
is performed.
listener
- the PropertyChangeListener to be addedremoveBeanPropertyChangeListener(PropertyChangeListener)
,
removeBeanPropertyChangeListener(String, PropertyChangeListener)
,
addBeanPropertyChangeListener(String, PropertyChangeListener)
,
getBeanPropertyChangeListeners()
public void removeBeanPropertyChangeListener(java.beans.PropertyChangeListener listener)
If listener is null
, no exception is thrown and no action is performed.
listener
- the PropertyChangeListener to be removedaddBeanPropertyChangeListener(PropertyChangeListener)
,
addBeanPropertyChangeListener(String, PropertyChangeListener)
,
removeBeanPropertyChangeListener(String, PropertyChangeListener)
,
getBeanPropertyChangeListeners()
public void addBeanPropertyChangeListener(java.lang.String propertyName, java.beans.PropertyChangeListener listener)
The listener will be notified if and only if this BeanAdapter's
current bean changes the specified property. It'll not be notified
if the bean changes. If you want to observe property changes and
bean changes, you may observe the ValueModel that adapts this property
- as returned by #getValueModel(String)
.
Note that if the bean is inheriting a bound property, then no event will be fired in response to a change in the inherited property.
If listener is null
, no exception is thrown and no action is performed.
propertyName
- one of the property names listed abovelistener
- the PropertyChangeListener to be addedremoveBeanPropertyChangeListener(String, PropertyChangeListener)
,
addBeanPropertyChangeListener(String, PropertyChangeListener)
,
getBeanPropertyChangeListeners(String)
public void removeBeanPropertyChangeListener(java.lang.String propertyName, java.beans.PropertyChangeListener listener)
If listener is null
, no exception is thrown and no action is performed.
propertyName
- a valid property namelistener
- the PropertyChangeListener to be removedaddBeanPropertyChangeListener(String, PropertyChangeListener)
,
removeBeanPropertyChangeListener(PropertyChangeListener)
,
getBeanPropertyChangeListeners(String)
public java.beans.PropertyChangeListener[] getBeanPropertyChangeListeners()
PropertyChangeListener
s
or an empty array if no property change
listeners are currently registeredaddBeanPropertyChangeListener(PropertyChangeListener)
,
removeBeanPropertyChangeListener(PropertyChangeListener)
,
getBeanPropertyChangeListeners(String)
,
PropertyChangeSupport.getPropertyChangeListeners()
public java.beans.PropertyChangeListener[] getBeanPropertyChangeListeners(java.lang.String propertyName)
propertyName
- the name of the property to lookup listenersPropertyChangeListeners
associated with
the named property or an empty array if no listeners have
been addedaddBeanPropertyChangeListener(String, PropertyChangeListener)
,
removeBeanPropertyChangeListener(String, PropertyChangeListener)
,
getBeanPropertyChangeListeners()
public void release()
null
and if bean property changes are observed.
Also removes all listeners from the bean that have been registered
with #addBeanPropertyChangeListener
before.
BeanAdapters that observe changes have a PropertyChangeListener
registered with the target bean. Hence, a bean has a reference
to all BeanAdapters that observe it. To avoid memory leaks
it is recommended to remove this listener if the bean lives much longer
than the BeanAdapter, enabling the garbage collector to remove the adapter.
To do so, you can call setBean(null)
or set the
bean channel's value to null.
As an alternative you can use event listener lists in your beans
that implement references with WeakReference
.
Setting the bean to null has side-effects, for example the adapter
fires a change event for the bound property bean and other properties.
And the value of ValueModel's vended by this adapter may change.
However, typically this is fine and setting the bean to null
is the first choice for removing the reference from the bean to the adapter.
Another way to clear the reference from the target bean is
to call #release
; it has no side-effects.
Since version 2.0.4 it is safe to call this method multiple times, however, the adapter must not be used anymore once #release has been called.
setBean(Object)
,
WeakReference
Copyright © 2002-2010 JGoodies Karsten Lentzsch. All Rights Reserved.