/** * Copyright (C) 2015 Valkyrie RCP * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.valkyriercp.binding.value.support; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.valkyriercp.binding.value.ValueChangeDetector; import org.valkyriercp.binding.value.ValueModel; import org.valkyriercp.core.support.AbstractPropertyChangePublisher; import org.valkyriercp.util.ValkyrieRepository; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; /** * An abstract class that minimizes the effort required to implement * the {@link ValueModel} interface. It provides convenience methods * to convert boolean, double, float, int, and long to their * corresponding Object values. * * <p>Subclasses must implement <code>getValue()</code> and * <code>setValue(Object)</code> to get and set the observable value. * * @author Karsten Lentzsch * @author Keith Donald * @author Oliver Hutchison */ public abstract class AbstractValueModel extends AbstractPropertyChangePublisher implements ValueModel { protected final Log logger = LogFactory.getLog(getClass()); private final ThreadLocal listenerToSkipHolder = new ThreadLocal(); private ValueChangeDetector valueChangeDetector; public final void setValueSilently(Object newValue, PropertyChangeListener listenerToSkip) { // We need to keep track of listenerToSkip on a per thread basis as it's // possible that this value model may be accessed from multiple threads. final Object oldListenerToSkip = listenerToSkipHolder.get(); try { listenerToSkipHolder.set(listenerToSkip); setValue(newValue); } finally { listenerToSkipHolder.set(oldListenerToSkip); } } public final void addValueChangeListener(PropertyChangeListener listener) { addPropertyChangeListener(VALUE_PROPERTY, listener); } public final void removeValueChangeListener(PropertyChangeListener listener) { removePropertyChangeListener(VALUE_PROPERTY, listener); } /** * This method can be called when it in necessary to send a * PropertyChangeEvent to any registered PropertyChangeListeners * even though the encapsulated value has not changed. * * FIXME: This needs a much better name! */ protected void fireValueChangeWhenStillEqual() { Object value = getValue(); fireValueChangeEvent(value, value); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the boolean value before the change * @param newValue the boolean value after the change */ protected final void fireValueChange(boolean oldValue, boolean newValue) { fireValueChange(Boolean.valueOf(oldValue), Boolean.valueOf(newValue)); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the int value before the change * @param newValue the int value after the change */ protected final void fireValueChange(int oldValue, int newValue) { fireValueChange(new Integer(oldValue), new Integer(newValue)); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the long value before the change * @param newValue the long value after the change */ protected final void fireValueChange(long oldValue, long newValue) { fireValueChange(new Long(oldValue), new Long(newValue)); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the double value before the change * @param newValue the double value after the change */ protected final void fireValueChange(double oldValue, double newValue) { fireValueChange(new Double(oldValue), new Double(newValue)); } /** * Notifies all listeners that have registered interest for * notification on this event type. The event instance * is lazily created using the parameters passed into * the fire method. * * @param oldValue the float value before the change * @param newValue the float value after the change */ protected void fireValueChange(Object oldValue, Object newValue) { if (hasValueChanged(oldValue, newValue)) { fireValueChangeEvent(oldValue, newValue); } } /** * Delegates to configured <code>ValueChangeDetector</code>. */ protected boolean hasValueChanged(Object oldValue, Object newValue) { return getValueChangeDetector().hasValueChanged(oldValue, newValue); } /** * Notifies all listeners that have registered interest for * notification on this event type. This method does not check if there is any change * between the old and new value unlike the various fireValueChanged() methods. */ protected void fireValueChangeEvent(Object oldValue, Object newValue) { if (logger.isDebugEnabled()) { logger.debug("Firing value changed event. Old value='" + oldValue + "' new value='" + newValue + "'"); } final PropertyChangeListener[] propertyChangeListeners = getPropertyChangeListeners(VALUE_PROPERTY); if (propertyChangeListeners.length > 0) { final Object listenerToSkip = listenerToSkipHolder.get(); final PropertyChangeEvent propertyChangeEvent = new PropertyChangeEvent(this, VALUE_PROPERTY, oldValue, newValue); for (PropertyChangeListener listener : propertyChangeListeners) { if (listener != listenerToSkip) { listener.propertyChange(propertyChangeEvent); } } } } /** * Set the object that will be used to detect changes between two values. * @param valueChangeDetector to use */ public void setValueChangeDetector(ValueChangeDetector valueChangeDetector) { this.valueChangeDetector = valueChangeDetector; } /** * Get the installed value change detector. If none has been directly installed then * get the one configured in the application context. * @return value change detector to use */ protected ValueChangeDetector getValueChangeDetector() { if(valueChangeDetector == null) return ValkyrieRepository.getInstance().getApplicationConfig().valueChangeDetector(); else return valueChangeDetector; } }