com.jgoodies.binding.util.ChangeTracker Maven / Gradle / Ivy
Show all versions of jgoodies-binding Show documentation
/*
* Copyright (c) 2002-2015 JGoodies Software GmbH. All Rights Reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* o Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
*
* o Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
*
* o Neither the name of JGoodies Software GmbH nor the names of
* its contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
* EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package com.jgoodies.binding.util;
import static com.jgoodies.common.base.Preconditions.checkNotNull;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import com.jgoodies.binding.beans.BeanUtils;
import com.jgoodies.binding.beans.Model;
import com.jgoodies.binding.beans.PropertyNotBindableException;
import com.jgoodies.binding.value.ValueModel;
/**
* Tracks changes in a set of bound bean properties. The tracker itself
* provides a read-only bound bean property changed that indicates
* whether one of the observed properties has changed. The changed state
* can be reset to {@code false} using {@code #reset}.
*
* The tracker can observe readable bound bean properties if and only if
* the bean provides the optional support for listening on named properties
* as described in section 7.4.5 of the
* Java Bean
* Specification. The bean class must provide the following pair of methods:
*
* public void addPropertyChangeListener(String name, PropertyChangeListener l);
* public void removePropertyChangeListener(String name, PropertyChangeListener l);
*
*
* Example:
* ChangeTracker tracker = new ChangeTracker();
* tracker.observe(address, "street");
* tracker.observe(address, "city");
* tracker.addPropertyChangeListener(new PropertyChangeListener() {
*
* public void propertyChange(PropertyChangeEvent evt) {
* System.out.println("Change state: " + evt.getNewValue());
* }
* });
*
* // Change the first ValueModel
* System.out.println(tracker.isChanged()); // Prints "false"
* address.setStreet("Belsenplatz"); // Prints "Change state: true"
* System.out.println(tracker.isChanged()); // Prints "true"
* tracker.reset(); // Prints "Change state: false"
* System.out.println(tracker.isChanged()); // Prints "false"
*
*
* Note: The classes {@code BeanAdapter} and
* {@code PresentationModel} already provide support for tracking changes.
* Typical binding code can use these classes and there seems to be no need
* to use the ChangeTracker.
*
* @author Karsten Lentzsch
* @version $Revision: 1.8 $
*
* @see ValueModel
*/
public final class ChangeTracker extends Model {
// Property Names *********************************************************
/**
* The name of the read-only bound bean property that
* indicates whether one of the observed properties has changed.
*
* @see #isChanged()
*/
public static final String PROPERTY_CHANGED = "changed";
// Instance Fields ********************************************************
/**
* Listens to property changes and updates the changed property.
*/
private final PropertyChangeListener updateHandler;
/**
* Indicates whether a registered model has changed.
*/
private boolean changed = false;
// Instance Creation *****************************************************
/**
* Constructs a change tracker with change state set to {@code false}.
*/
public ChangeTracker() {
updateHandler = new UpdateHandler();
}
// Accessing the Changed State ********************************************
/**
* Answers whether one of the registered ValueModels has changed
* since this tracker has been reset last time.
*
* @return true if an observed property has changed since the last reset
*/
public boolean isChanged() {
return changed;
}
/**
* Resets this tracker's changed state to {@code false}.
*/
public void reset() {
setChanged(false);
}
private void setChanged(boolean newValue) {
boolean oldValue = isChanged();
changed = newValue;
firePropertyChange(PROPERTY_CHANGED, oldValue, newValue);
}
// Registering *************************************************************
/**
* Observes the specified readable bound bean property in the given bean.
*
* @param bean the bean to be observed
* @param propertyName the name of the readable bound bean property
* @throws NullPointerException if the bean or propertyName is null
* @throws PropertyNotBindableException if this tracker can't add
* the PropertyChangeListener from the bean
*
* @see #retractInterestFor(Object, String)
*/
public void observe(Object bean, String propertyName) {
checkNotNull(bean, "The bean must not be null.");
checkNotNull(propertyName, "The property name must not be null.");
BeanUtils.addPropertyChangeListener(bean, propertyName, updateHandler);
}
/**
* Observes value changes in the given ValueModel.
*
* @param valueModel the ValueModel to observe
* @throws NullPointerException if the valueModel is null
*
* @see #retractInterestFor(ValueModel)
*/
public void observe(ValueModel valueModel) {
checkNotNull(valueModel, "The ValueModel must not be null.");
valueModel.addValueChangeListener(updateHandler);
}
/**
* Retracts interest for the specified readable bound bean property
* in the given bean.
*
* @param bean the bean to be observed
* @param propertyName the name of the readable bound bean property
* @throws NullPointerException if the bean or propertyName is null
* @throws PropertyNotBindableException if this tracker can't remove
* the PropertyChangeListener from the bean
*
* @see #observe(Object, String)
*/
public void retractInterestFor(Object bean, String propertyName) {
checkNotNull(bean, "The bean must not be null.");
checkNotNull(propertyName, "The property name must not be null.");
BeanUtils.removePropertyChangeListener(bean, propertyName, updateHandler);
}
/**
* Retracts interest for value changes in the given ValueModel.
*
* @param valueModel the ValueModel to observe
* @throws NullPointerException if the valueModel is null
*
* @see #retractInterestFor(ValueModel)
*/
public void retractInterestFor(ValueModel valueModel) {
checkNotNull(valueModel, "The ValueModel must not be null.");
valueModel.removeValueChangeListener(updateHandler);
}
// Private Helper Code ****************************************************
/**
* Listens to model changes and updates the changed state.
*/
private final class UpdateHandler implements PropertyChangeListener {
/**
* A registered ValueModel has changed.
* Updates the changed state. If the property that changed is
* 'changed' we assume that this is another changed state and
* forward only changes to true. For all other property names,
* we just update our changed state to true.
*
* @param evt the event that describes the property change
*/
@Override
public void propertyChange(PropertyChangeEvent evt) {
String propertyName = evt.getPropertyName();
if (!PROPERTY_CHANGED.equals(propertyName)
|| ((Boolean) evt.getNewValue()).booleanValue()) {
setChanged(true);
}
}
}
}