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.util;
032:
033: import java.beans.PropertyChangeEvent;
034: import java.beans.PropertyChangeListener;
035:
036: import com.jgoodies.binding.beans.BeanUtils;
037: import com.jgoodies.binding.beans.Model;
038: import com.jgoodies.binding.beans.PropertyNotBindableException;
039: import com.jgoodies.binding.value.ValueModel;
040:
041: /**
042: * Tracks changes in a set of bound bean properties. The tracker itself
043: * provides a read-only bound bean property <em>changed</em> that indicates
044: * whether one of the observed properties has changed. The changed state
045: * can be reset to <code>false</code> using <code>#reset</code>.<p>
046: *
047: * The tracker can observe readable bound bean properties if and only if
048: * the bean provides the optional support for listening on named properties
049: * as described in section 7.4.5 of the
050: * <a href="http://java.sun.com/products/javabeans/docs/spec.html">Java Bean
051: * Specification</a>. The bean class must provide the following pair of methods:
052: * <pre>
053: * public void addPropertyChangeListener(String name, PropertyChangeListener l);
054: * public void removePropertyChangeListener(String name, PropertyChangeListener l);
055: * </pre><p>
056: *
057: * <strong>Example:</strong><pre>
058: * ChangeTracker tracker = new ChangeTracker();
059: * tracker.observe(address, "street");
060: * tracker.observe(address, "city");
061: * tracker.addPropertyChangeListener(new PropertyChangeListener() {
062: *
063: * public void propertyChange(PropertyChangeEvent evt) {
064: * System.out.println("Change state: " + evt.getNewValue());
065: * }
066: * });
067: *
068: * // Change the first ValueModel
069: * System.out.println(tracker.isChanged()); // Prints "false"
070: * address.setStreet("Belsenplatz"); // Prints "Change state: true"
071: * System.out.println(tracker.isChanged()); // Prints "true"
072: * tracker.reset(); // Prints "Change state: false"
073: * System.out.println(tracker.isChanged()); // Prints "false"
074: * </pre><p>
075: *
076: * <strong>Note:</strong> The classes <code>BeanAdapter</code> and
077: * <code>PresentationModel</code> already provide support for tracking changes.
078: * Typical binding code can use these classes and there seems to be no need
079: * to use the ChangeTracker.
080: *
081: * @author Karsten Lentzsch
082: * @version $Revision: 1.3 $
083: *
084: * @see ValueModel
085: */
086: public final class ChangeTracker extends Model {
087:
088: /**
089: * The name of the read-only bound bean property that
090: * indicates whether one of the observed properties has changed.
091: *
092: * @see #isChanged()
093: */
094: public static final String PROPERTYNAME_CHANGED = "changed";
095:
096: /**
097: * Listens to property changes and updates the <em>changed</em> property.
098: */
099: private final PropertyChangeListener updateHandler;
100:
101: /**
102: * Indicates whether a registered model has changed.
103: */
104: private boolean changed = false;
105:
106: // Instance Creation *****************************************************
107:
108: /**
109: * Constructs a change tracker with change state set to <code>false</code>.
110: */
111: public ChangeTracker() {
112: updateHandler = new UpdateHandler();
113: }
114:
115: // Accessing the Changed State ********************************************
116:
117: /**
118: * Answers whether one of the registered ValueModels has changed
119: * since this tracker has been reset last time.
120: *
121: * @return true if an observed property has changed since the last reset
122: */
123: public boolean isChanged() {
124: return changed;
125: }
126:
127: /**
128: * Resets this tracker's changed state to <code>false</code>.
129: */
130: public void reset() {
131: setChanged(false);
132: }
133:
134: private void setChanged(boolean newValue) {
135: boolean oldValue = isChanged();
136: changed = newValue;
137: firePropertyChange(PROPERTYNAME_CHANGED, oldValue, newValue);
138: }
139:
140: // Registering *************************************************************
141:
142: /**
143: * Observes the specified readable bound bean property in the given bean.
144: *
145: * @param bean the bean to be observed
146: * @param propertyName the name of the readable bound bean property
147: * @throws NullPointerException if the bean or propertyName is null
148: * @throws PropertyNotBindableException if this tracker can't add
149: * the PropertyChangeListener from the bean
150: *
151: * @see #retractInterestFor(Object, String)
152: */
153: public void observe(Object bean, String propertyName) {
154: if (bean == null)
155: throw new NullPointerException("The bean must not be null.");
156: if (propertyName == null)
157: throw new NullPointerException(
158: "The property name must not be null.");
159:
160: BeanUtils.addPropertyChangeListener(bean, propertyName,
161: updateHandler);
162: }
163:
164: /**
165: * Observes value changes in the given ValueModel.
166: *
167: * @param valueModel the ValueModel to observe
168: * @throws NullPointerException if the valueModel is null
169: *
170: * @see #retractInterestFor(ValueModel)
171: */
172: public void observe(ValueModel valueModel) {
173: if (valueModel == null)
174: throw new NullPointerException(
175: "The ValueModel must not be null.");
176: valueModel.addValueChangeListener(updateHandler);
177: }
178:
179: /**
180: * Retracts interest for the specified readable bound bean property
181: * in the given bean.
182: *
183: * @param bean the bean to be observed
184: * @param propertyName the name of the readable bound bean property
185: * @throws NullPointerException if the bean or propertyName is null
186: * @throws PropertyNotBindableException if this tracker can't remove
187: * the PropertyChangeListener from the bean
188: *
189: * @see #observe(Object, String)
190: */
191: public void retractInterestFor(Object bean, String propertyName) {
192: if (bean == null)
193: throw new NullPointerException("The bean must not be null.");
194: if (propertyName == null)
195: throw new NullPointerException(
196: "The property name must not be null.");
197:
198: BeanUtils.removePropertyChangeListener(bean, propertyName,
199: updateHandler);
200: }
201:
202: /**
203: * Retracts interest for value changes in the given ValueModel.
204: *
205: * @param valueModel the ValueModel to observe
206: * @throws NullPointerException if the valueModel is null
207: *
208: * @see #retractInterestFor(ValueModel)
209: */
210: public void retractInterestFor(ValueModel valueModel) {
211: if (valueModel == null)
212: throw new NullPointerException(
213: "The ValueModel must not be null.");
214: valueModel.removeValueChangeListener(updateHandler);
215: }
216:
217: // Private Helper Code ****************************************************
218:
219: /**
220: * Listens to model changes and updates the changed state.
221: */
222: private final class UpdateHandler implements PropertyChangeListener {
223:
224: /**
225: * A registered ValueModel has changed.
226: * Updates the changed state. If the property that changed is
227: * 'changed' we assume that this is another changed state and
228: * forward only changes to true. For all other property names,
229: * we just update our changed state to true.
230: *
231: * @param evt the event that describes the property change
232: */
233: public void propertyChange(PropertyChangeEvent evt) {
234: String propertyName = evt.getPropertyName();
235: if (!PROPERTYNAME_CHANGED.equals(propertyName)
236: || ((Boolean) evt.getNewValue()).booleanValue()) {
237: setChanged(true);
238: }
239: }
240: }
241:
242: }
|