A ValueModel that wraps another ValueModel, the subject,
and delays changes of the subject's value. Returns the subject's value
until a value has been set. The buffered value is not written to the
subject until the trigger channel changes to
Boolean.TRUE
.
The buffered value can be flushed by changing the trigger channel value
to
Boolean.FALSE
. Note that the commit and flush events
are performed only if the trigger channel fires a change event. Since a
plain ValueHolder fires no property change event if a value is set that has
been set before, it is recommended to use a
Trigger
instead
and invoke its
#triggerCommit
and
triggerFlush
methods.
The BufferedValueModel has been designed to behave much like its subject
when accessing the value. Therefore it throws all exceptions that would
arise when accessing the subject directly. Hence, attempts to read or
write a value while the subject is
null
are always rejected
with a
NullPointerException
.
This class provides the bound read-write properties
subject and
triggerChannel for the subject and trigger channel and a bound
read-only property
buffering for the buffering state.
The BufferedValueModel registers listeners with the subject and
trigger channel. It is recommended to remove these listeners by invoking
#release
if the subject and trigger channel live much longer
than this buffer. After
#release
has been called
you must not use the BufferedValueModel instance any longer.
As an alternative you may use event listener lists in subjects and
trigger channels that are based on
WeakReference
s.
If the subject value changes while this model is in buffering state
this change won't show through as this model's new value. If you want
to update the value whenever the subject value changes, register a
listener with the subject value and flush this model's trigger.
Constraints: The subject is of type
Object
,
the trigger channel value of type
Boolean
.
String paramString
protected @Override String paramString()
getSubject
public ValueModel getSubject()
Returns the subject, i.e. the underlying ValueModel that provides
the unbuffered value.
- the ValueModel that provides the unbuffered value
getTriggerChannel
public ValueModel getTriggerChannel()
Returns the ValueModel that is used to trigger commit and flush events.
- the ValueModel that is used to trigger commit and flush events
getValue
public Object getValue()
Returns the subject's value if no value has been set since the last
commit or flush, and returns the buffered value otherwise.
Attempts to read a value when no subject is set are rejected
with a NullPointerException.
- getValue in interface ValueModel
isBuffering
public boolean isBuffering()
Returns whether this model buffers a value or not, that is, whether
a value has been assigned since the last commit or flush.
- true if a value has been assigned since the last commit or flush
release
public void release()
Removes the PropertyChangeListeners from the subject and
trigger channel.
To avoid memory leaks it is recommended to invoke this method
if the subject and trigger channel live much longer than this buffer.
Once #release has been invoked the BufferedValueModel instance
must not be used any longer.
As an alternative you may use event listener lists in subjects and
trigger channels that are based on
WeakReference
s.
java.lang.ref.WeakReference
setSubject
public void setSubject(ValueModel newSubject)
Sets a new subject ValueModel, i.e. the model that provides
the unbuffered value. Notifies all listeners that the subject
property has changed.
newSubject
- the subject ValueModel to be set
setTriggerChannel
public void setTriggerChannel(ValueModel newTriggerChannel)
Sets the ValueModel that triggers the commit and flush events.
newTriggerChannel
- the ValueModel to be set as trigger channel
setValue
public void setValue(Object newBufferedValue)
Sets a new buffered value and turns this BufferedValueModel into
the buffering state. The buffered value is not provided to the
underlying model until the trigger channel indicates a commit.
Attempts to set a value when no subject is set are rejected
with a NullPointerException.
The above semantics is easy to understand, however it is tempting
to check the new value against the current subject value to avoid
that the buffer unnecessary turns into the buffering state. But here's
a problem. Let's say the subject value is "first" at buffer
creation time, and let's say the subject value has changed in the
meantime to "second". Now someone sets the value "second" to this buffer.
The subject value and the value to be set are equal. Shall we buffer?
Also, this decision would depend on the ability to read the subject.
The semantics would depend on the subject' state and capabilities.
It is often sufficient to observe the buffering state when enabling
or disabling a commit command button like "OK" or "Apply".
And later check the
changed state in a PresentationModel.
You may want to do better and may want to observe a property like
"defersTrueChange" that indicates whether flushing a buffer will
actually change the subject. But note that such a state may change
with subject value changes, which may be hard to understand for a user.
TODO: Consider adding an optimized execution path for the case
that this model is already in buffering state. In this case
the old buffered value can be used instead of invoking
#readBufferedOrSubjectValue()
.
- setValue in interface ValueModel
newBufferedValue
- the value to be buffered