001: /*
002: * Copyright (c) 2002-2007 JGoodies Karsten Lentzsch. All Rights Reserved.
003: *
004: * Redistribution and use in source and binary forms, with or without
005: * modification, are permitted provided that the following conditions are met:
006: *
007: * o Redistributions of source code must retain the above copyright notice,
008: * this list of conditions and the following disclaimer.
009: *
010: * o Redistributions in binary form must reproduce the above copyright notice,
011: * this list of conditions and the following disclaimer in the documentation
012: * and/or other materials provided with the distribution.
013: *
014: * o Neither the name of JGoodies Karsten Lentzsch nor the names of
015: * its contributors may be used to endorse or promote products derived
016: * from this software without specific prior written permission.
017: *
018: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
019: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
020: * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
021: * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
022: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
023: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
024: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
025: * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
026: * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
027: * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
028: * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
029: */
030:
031: package com.jgoodies.binding.value;
032:
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035:
036: /**
037: * A ValueModel that wraps another ValueModel, the subject,
038: * and delays changes of the subject's value. Returns the subject's value
039: * until a value has been set. The buffered value is not written to the
040: * subject until the trigger channel changes to <code>Boolean.TRUE</code>.
041: * The buffered value can be flushed by changing the trigger channel value
042: * to <code>Boolean.FALSE</code>. Note that the commit and flush events
043: * are performed only if the trigger channel fires a change event. Since a
044: * plain ValueHolder fires no property change event if a value is set that has
045: * been set before, it is recommended to use a {@link Trigger} instead
046: * and invoke its <code>#triggerCommit</code> and <code>triggerFlush</code>
047: * methods.<p>
048: *
049: * The BufferedValueModel has been designed to behave much like its subject
050: * when accessing the value. Therefore it throws all exceptions that would
051: * arise when accessing the subject directly. Hence, attempts to read or
052: * write a value while the subject is <code>null</code> are always rejected
053: * with a <code>NullPointerException</code>.<p>
054: *
055: * This class provides the bound read-write properties <em>subject</em> and
056: * <em>triggerChannel</em> for the subject and trigger channel and a bound
057: * read-only property <em>buffering</em> for the buffering state.<p>
058: *
059: * The BufferedValueModel registers listeners with the subject and
060: * trigger channel. It is recommended to remove these listeners by invoking
061: * <code>#release</code> if the subject and trigger channel live much longer
062: * than this buffer. After <code>#release</code> has been called
063: * you must not use the BufferedValueModel instance any longer.
064: * As an alternative you may use event listener lists in subjects and
065: * trigger channels that are based on <code>WeakReference</code>s.<p>
066: *
067: * If the subject value changes while this model is in buffering state
068: * this change won't show through as this model's new value. If you want
069: * to update the value whenever the subject value changes, register a
070: * listener with the subject value and flush this model's trigger.<p>
071: *
072: * <strong>Constraints:</strong> The subject is of type <code>Object</code>,
073: * the trigger channel value of type <code>Boolean</code>.
074: *
075: * @author Karsten Lentzsch
076: * @version $Revision: 1.9 $
077: *
078: * @see ValueModel
079: * @see ValueModel#getValue()
080: * @see ValueModel#setValue(Object)
081: */
082: public final class BufferedValueModel extends AbstractValueModel {
083:
084: // Names of the bound bean properties *************************************
085:
086: /**
087: * The name of the bound read-only bean property that indicates
088: * whether this models is buffering or in write-through state.
089: *
090: * @see #isBuffering()
091: */
092: public static final String PROPERTYNAME_BUFFERING = "buffering";
093:
094: /**
095: * The name of the bound read-write bean property for the subject.
096: *
097: * @see #getSubject()
098: * @see #setSubject(ValueModel)
099: */
100: public static final String PROPERTYNAME_SUBJECT = "subject";
101:
102: /**
103: * The name of the bound read-write bean property for the trigger channel.
104: *
105: * @see #getTriggerChannel()
106: * @see #setTriggerChannel(ValueModel)
107: */
108: public static final String PROPERTYNAME_TRIGGER_CHANNEL = "triggerChannel";
109:
110: // ************************************************************************
111:
112: /**
113: * Holds the subject that provides the underlying value
114: * of type <code>Object</code>.
115: */
116: private ValueModel subject;
117:
118: /**
119: * Holds the three-state trigger of type <code>Boolean</code>.
120: */
121: private ValueModel triggerChannel;
122:
123: /**
124: * Holds the buffered value. This value is ignored if we are not buffering.
125: */
126: private Object bufferedValue;
127:
128: /**
129: * Indicates whether a value has been assigned since the last trigger change.
130: */
131: private boolean valueAssigned;
132:
133: /**
134: * Holds a PropertyChangeListener that observes subject value changes.
135: */
136: private final ValueChangeHandler valueChangeHandler;
137:
138: /**
139: * Holds a PropertyChangeListener that observes trigger changes.
140: */
141: private final TriggerChangeHandler triggerChangeHandler;
142:
143: // Instance Creation ****************************************************
144:
145: /**
146: * Constructs a BufferedValueModel on the given subject
147: * using the given trigger channel.
148: *
149: * @param subject the value model to be buffered
150: * @param triggerChannel the value model that triggers the commit or flush event
151: * @throws NullPointerException if the triggerChannel is <code>null</code>
152: */
153: public BufferedValueModel(ValueModel subject,
154: ValueModel triggerChannel) {
155: valueChangeHandler = new ValueChangeHandler();
156: triggerChangeHandler = new TriggerChangeHandler();
157: setSubject(subject);
158: setTriggerChannel(triggerChannel);
159: setBuffering(false);
160: }
161:
162: // Accessing the Subject and Trigger Channel ******************************
163:
164: /**
165: * Returns the subject, i.e. the underlying ValueModel that provides
166: * the unbuffered value.
167: *
168: * @return the ValueModel that provides the unbuffered value
169: */
170: public ValueModel getSubject() {
171: return subject;
172: }
173:
174: /**
175: * Sets a new subject ValueModel, i.e. the model that provides
176: * the unbuffered value. Notifies all listeners that the <i>subject</i>
177: * property has changed.
178: *
179: * @param newSubject the subject ValueModel to be set
180: */
181: public void setSubject(ValueModel newSubject) {
182: ValueModel oldSubject = getSubject();
183: ReadAccessResult oldReadValue = readBufferedOrSubjectValue();
184: Object oldValue = oldReadValue.value;
185: if (oldSubject != null) {
186: oldSubject.removeValueChangeListener(valueChangeHandler);
187: }
188: subject = newSubject;
189: if (newSubject != null) {
190: newSubject.addValueChangeListener(valueChangeHandler);
191: }
192: firePropertyChange(PROPERTYNAME_SUBJECT, oldSubject, newSubject);
193: if (isBuffering())
194: return;
195:
196: ReadAccessResult newReadValue = readBufferedOrSubjectValue();
197: Object newValue = newReadValue.value;
198: // TODO: Check if the following conditional is valid.
199: // Note that the old and/or new value may be null
200: // just because the property is read-only.
201: if (oldValue != null || newValue != null) {
202: fireValueChange(oldValue, newValue, true);
203: }
204: }
205:
206: /**
207: * Returns the ValueModel that is used to trigger commit and flush events.
208: *
209: * @return the ValueModel that is used to trigger commit and flush events
210: */
211: public ValueModel getTriggerChannel() {
212: return triggerChannel;
213: }
214:
215: /**
216: * Sets the ValueModel that triggers the commit and flush events.
217: *
218: * @param newTriggerChannel the ValueModel to be set as trigger channel
219: * @throws NullPointerException if the newTriggerChannel is <code>null</code>
220: */
221: public void setTriggerChannel(ValueModel newTriggerChannel) {
222: if (newTriggerChannel == null)
223: throw new NullPointerException(
224: "The trigger channel must not be null.");
225:
226: ValueModel oldTriggerChannel = getTriggerChannel();
227: if (oldTriggerChannel != null) {
228: oldTriggerChannel
229: .removeValueChangeListener(triggerChangeHandler);
230: }
231: triggerChannel = newTriggerChannel;
232: //if (newTriggerChannel != null) {
233: newTriggerChannel.addValueChangeListener(triggerChangeHandler);
234: //}
235: firePropertyChange(PROPERTYNAME_TRIGGER_CHANNEL,
236: oldTriggerChannel, newTriggerChannel);
237: }
238:
239: // Implementing the ValueModel Interface ********************************
240:
241: /**
242: * Returns the subject's value if no value has been set since the last
243: * commit or flush, and returns the buffered value otherwise.
244: * Attempts to read a value when no subject is set are rejected
245: * with a NullPointerException.
246: *
247: * @return the buffered value
248: * @throws NullPointerException if no subject is set
249: */
250: public Object getValue() {
251: if (subject == null)
252: throw new NullPointerException(
253: "The subject must not be null "
254: + "when reading a value from a BufferedValueModel.");
255:
256: return isBuffering() ? bufferedValue : subject.getValue();
257: }
258:
259: /**
260: * Sets a new buffered value and turns this BufferedValueModel into
261: * the buffering state. The buffered value is not provided to the
262: * underlying model until the trigger channel indicates a commit.
263: * Attempts to set a value when no subject is set are rejected
264: * with a NullPointerException.<p>
265: *
266: * The above semantics is easy to understand, however it is tempting
267: * to check the new value against the current subject value to avoid
268: * that the buffer unnecessary turns into the buffering state. But here's
269: * a problem. Let's say the subject value is "first" at buffer
270: * creation time, and let's say the subject value has changed in the
271: * meantime to "second". Now someone sets the value "second" to this buffer.
272: * The subject value and the value to be set are equal. Shall we buffer?
273: * Also, this decision would depend on the ability to read the subject.
274: * The semantics would depend on the subject' state and capabilities.<p>
275: *
276: * It is often sufficient to observe the buffering state when enabling
277: * or disabling a commit command button like "OK" or "Apply".
278: * And later check the <em>changed</em> state in a PresentationModel.
279: * You may want to do better and may want to observe a property like
280: * "defersTrueChange" that indicates whether flushing a buffer will
281: * actually change the subject. But note that such a state may change
282: * with subject value changes, which may be hard to understand for a user.<p>
283: *
284: * TODO: Consider adding an optimized execution path for the case
285: * that this model is already in buffering state. In this case
286: * the old buffered value can be used instead of invoking
287: * <code>#readBufferedOrSubjectValue()</code>.
288: *
289: * @param newBufferedValue the value to be buffered
290: * @throws NullPointerException if no subject is set
291: */
292: public void setValue(Object newBufferedValue) {
293: if (subject == null)
294: throw new NullPointerException(
295: "The subject must not be null "
296: + "when setting a value to a BufferedValueModel.");
297:
298: ReadAccessResult oldReadValue = readBufferedOrSubjectValue();
299: Object oldValue = oldReadValue.value;
300: bufferedValue = newBufferedValue;
301: setBuffering(true);
302: if (oldReadValue.readable && oldValue == newBufferedValue)
303: return;
304: fireValueChange(oldValue, newBufferedValue, true);
305: }
306:
307: /**
308: * Tries to lookup the current buffered or subject value
309: * and returns this value plus a marker that indicates
310: * whether the read-access succeeded or failed.
311: * The latter situation arises in an attempt to read a value from
312: * a write-only subject if this BufferedValueModel is not buffering
313: * and if this model changes its subject.
314: *
315: * @return the current value plus a boolean that indicates the success or failure
316: */
317: private ReadAccessResult readBufferedOrSubjectValue() {
318: try {
319: Object value = getValue(); // May fail with write-only models
320: return new ReadAccessResult(value, true);
321: } catch (Exception e) {
322: return new ReadAccessResult(null, false);
323: }
324: }
325:
326: // Releasing PropertyChangeListeners **************************************
327:
328: /**
329: * Removes the PropertyChangeListeners from the subject and
330: * trigger channel.<p>
331: *
332: * To avoid memory leaks it is recommended to invoke this method
333: * if the subject and trigger channel live much longer than this buffer.
334: * Once #release has been invoked the BufferedValueModel instance
335: * must not be used any longer.<p>
336: *
337: * As an alternative you may use event listener lists in subjects and
338: * trigger channels that are based on <code>WeakReference</code>s.
339: *
340: * @see java.lang.ref.WeakReference
341: */
342: public void release() {
343: ValueModel aSubject = getSubject();
344: if (aSubject != null) {
345: aSubject.removeValueChangeListener(valueChangeHandler);
346: }
347: ValueModel aTriggerChannel = getTriggerChannel();
348: if (aTriggerChannel != null) {
349: aTriggerChannel
350: .removeValueChangeListener(triggerChangeHandler);
351: }
352: }
353:
354: // Misc *****************************************************************
355:
356: /**
357: * Returns whether this model buffers a value or not, that is, whether
358: * a value has been assigned since the last commit or flush.
359: *
360: * @return true if a value has been assigned since the last commit or flush
361: */
362: public boolean isBuffering() {
363: return valueAssigned;
364: }
365:
366: private void setBuffering(boolean newValue) {
367: boolean oldValue = isBuffering();
368: valueAssigned = newValue;
369: firePropertyChange(PROPERTYNAME_BUFFERING, oldValue, newValue);
370: }
371:
372: /**
373: * Sets the buffered value as new subject value - if any value has been set.
374: * After this commit this BufferedValueModel behaves as if no value
375: * has been set before. This method is invoked if the trigger has changed
376: * to {@code Boolean.TRUE}.<p>
377: *
378: * Since the subject's value is assigned <em>after</em> the buffer marker
379: * is reset, subject change notifications will be handled. In this case
380: * the subject's old value is not this BufferedValueModel's old value;
381: * instead the old value reported to listeners of this model
382: * is the formerly buffered value.
383: *
384: * @throws NullPointerException if no subject is set
385: */
386: private void commit() {
387: if (isBuffering()) {
388: setBuffering(false);
389: valueChangeHandler.oldValue = bufferedValue;
390: subject.setValue(bufferedValue);
391: valueChangeHandler.oldValue = null;
392: } else if (subject == null)
393: throw new NullPointerException(
394: "The subject must not be null "
395: + "while committing a value in a BufferedValueModel.");
396: }
397:
398: /**
399: * Flushes the buffered value. This method is invoked if the trigger
400: * has changed to {@code Boolean.FALSE}. After this flush
401: * this BufferedValueModel behaves as if no value has been set before.<p>
402: *
403: * TODO: Check whether we need to use #getValueSafe instead of #getValue.
404: *
405: * @throws NullPointerException if no subject is set
406: */
407: private void flush() {
408: Object oldValue = getValue();
409: setBuffering(false);
410: Object newValue = getValue();
411: fireValueChange(oldValue, newValue, true);
412: }
413:
414: // Helper Class ***********************************************************
415:
416: /**
417: * Describes the result of a subject value read-access plus a marker
418: * that indicates if the value could be read or not. The latter is
419: * used in <code>#setValue</code> to suppress some unnecessary
420: * change notifications in case the value could be read successfully.
421: *
422: * @see BufferedValueModel#setValue(Object)
423: */
424: private static final class ReadAccessResult {
425:
426: final Object value;
427: final boolean readable;
428:
429: private ReadAccessResult(Object value, boolean readable) {
430: this .value = value;
431: this .readable = readable;
432: }
433:
434: }
435:
436: // Event Handling *********************************************************
437:
438: /**
439: * Listens to changes of the subject.
440: */
441: private final class ValueChangeHandler implements
442: PropertyChangeListener {
443:
444: Object oldValue;
445:
446: /**
447: * The subject's value has changed. Notifies this BufferedValueModel's
448: * listeners iff we are not buffering, does nothing otherwise.<p>
449: *
450: * @param evt the property change event to be handled
451: */
452: public void propertyChange(PropertyChangeEvent evt) {
453: if (!isBuffering()) {
454: fireValueChange(oldValue != null ? oldValue : evt
455: .getOldValue(), evt.getNewValue(), true);
456: }
457: }
458: }
459:
460: /**
461: * Listens to changes of the trigger channel.
462: */
463: private final class TriggerChangeHandler implements
464: PropertyChangeListener {
465:
466: /**
467: * The trigger has been changed. Commits or flushes the buffered value.
468: *
469: * @param evt the property change event to be handled
470: */
471: public void propertyChange(PropertyChangeEvent evt) {
472: if (Boolean.TRUE.equals(evt.getNewValue()))
473: commit();
474: else if (Boolean.FALSE.equals(evt.getNewValue()))
475: flush();
476: }
477: }
478:
479: }
|