/**
* 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.form.support;
import org.valkyriercp.binding.value.DirtyTrackingValueModel;
import org.valkyriercp.binding.value.ValueChangeDetector;
import org.valkyriercp.binding.value.ValueModel;
import org.valkyriercp.binding.value.support.AbstractValueModelWrapper;
import org.valkyriercp.binding.value.support.ValueHolder;
import org.valkyriercp.util.EventListenerListHelper;
import org.valkyriercp.util.ValkyrieRepository;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.Iterator;
/**
* <p>
* A value model wrapper that mediates between the (wrapped) data value model
* and the derived view value model. Allows for value change event delivery to
* be disabled.
* </p>
*
* <p>
* Use the provided method {@link #setDeliverValueChangeEvents(boolean)} to
* enable/disable the event mechanism. This makes it possible to change all
* valueModels and only then fire all events (as it is used in
* {@link AbstractFormModel}). Events are handled internally by the
* {@link #dirtyChangeListeners} and the {@link #mediatedValueHolder}.
* </p>
*
* <p>
* As this is also a {@link DirtyTrackingValueModel}, the implementation allows
* reverting to the original value by using {@link #revertToOriginal()}, which
* uses the value stored in {@link #originalValue} to reset the
* wrappedValueModel. This originalValue is updated to hold the wrappedValue
* when using {@link #clearDirty()}.
* </p>
*
* <p>Small sketch to illustrate the positioning and usage:</p>
* <pre>
* <setup>
* External actor -> FormModelMediatingValueModel -> wrappedValueModel
* holds originalValue = ori wrappedValue = ori
* events = enabled
*
* <use case>
* events disabled -> events = disabled -> wrappedValue = ori
* write value A -> delagates to wrappedModel -> wrappedValue = A
* originalValue = ori
* update dirty state
* events enabled -> events = enabled -> wrappedValue = A
* sends events (dirty...)
* clearDirty -> originalValue = A -> wrappedValue = A
* OR
* revertToOriginal-> set originalValue on wrapped -> wrappedValue = ori
* update dirty state
* </pre>
*
* @author Oliver Hutchison
*/
public class FormModelMediatingValueModel extends AbstractValueModelWrapper implements DirtyTrackingValueModel,
PropertyChangeListener {
/** Holds all propertyChangeListeners that are interested in Dirty changes. */
private final EventListenerListHelper dirtyChangeListeners = new EventListenerListHelper(
PropertyChangeListener.class);
/** Allows to turn off the tracking mechanism. */
private boolean deliverValueChangeEvents = true;
/** Holds the originalValue. Used to register listeners. */
private final ValueHolder mediatedValueHolder;
/** The original value of the wrapped ValueModel. */
private Object originalValue;
/** Previous dirty state. */
private boolean oldDirty;
/** Dirty tracking can be disabled. */
private final boolean trackDirty;
/**
* Constructor which defaults <code>trackDirty=true</code>.
*
* @param propertyValueModel the ValueModel to mediate.
*/
public FormModelMediatingValueModel(ValueModel propertyValueModel) {
this(propertyValueModel, true);
}
/**
* Constructor.
*
* @param propertyValueModel the valueModel to mediate.
* @param trackDirty disable/enable dirty tracking.
*/
public FormModelMediatingValueModel(ValueModel propertyValueModel, boolean trackDirty) {
super(propertyValueModel);
super.addValueChangeListener(this);
this.originalValue = getValue();
this.mediatedValueHolder = new ValueHolder(originalValue);
this.trackDirty = trackDirty;
}
public void setValueSilently(Object value, PropertyChangeListener listenerToSkip) {
super.setValueSilently(value, this);
if (deliverValueChangeEvents) {
mediatedValueHolder.setValueSilently(value, listenerToSkip);
updateDirtyState();
}
}
// called by the wrapped value model
public void propertyChange(PropertyChangeEvent evt) {
originalValue = getValue();
if (deliverValueChangeEvents) {
mediatedValueHolder.setValue(originalValue);
updateDirtyState();
}
}
/**
* <p>
* Enable/disable the event mechanism. Makes it possible to control the
* timing of event firing (delay the events).
* </p>
*
* <p>
* When disabling, no dirty events will be fired and the mediating
* valueHolder will not set it's value. The latter results in not firing
* other events like valueChangedEvents.
* </p>
* <p>
* When enabling, original (stored) value is compared to the newer value in
* the wrapped model and the necessary events are fired
* (dirty/valueChanged).
* <p>
*
* @param deliverValueChangeEvents boolean to enable/disable event
* mechanism.
*/
public void setDeliverValueChangeEvents(boolean deliverValueChangeEvents) {
boolean oldDeliverValueChangeEvents = this.deliverValueChangeEvents;
this.deliverValueChangeEvents = deliverValueChangeEvents;
if (!oldDeliverValueChangeEvents && deliverValueChangeEvents) {
mediatedValueHolder.setValue(getValue());
updateDirtyState();
}
}
public boolean isDirty() {
return trackDirty && getValueChangeDetector().hasValueChanged(originalValue, getValue());
}
public void clearDirty() {
if (isDirty()) {
originalValue = getValue();
updateDirtyState();
}
}
public void revertToOriginal() {
if (isDirty()) {
setValue(originalValue);
}
}
/**
* Check the dirty state and fire events if needed.
*/
protected void updateDirtyState() {
boolean dirty = isDirty();
if (oldDirty != dirty) {
oldDirty = dirty;
firePropertyChange(DIRTY_PROPERTY, !dirty, dirty);
}
}
/**
* @return a {@link ValueChangeDetector} to use when checking the dirty
* state.
*/
protected ValueChangeDetector getValueChangeDetector() {
return ValkyrieRepository.getInstance().getApplicationConfig().valueChangeDetector();
}
public void addValueChangeListener(PropertyChangeListener listener) {
mediatedValueHolder.addValueChangeListener(listener);
}
public void removeValueChangeListener(PropertyChangeListener listener) {
mediatedValueHolder.removeValueChangeListener(listener);
}
public void addPropertyChangeListener(PropertyChangeListener listener) {
throw new UnsupportedOperationException("not implemented");
}
public void addPropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (DIRTY_PROPERTY.equals(propertyName)) {
dirtyChangeListeners.add(listener);
}
else if (VALUE_PROPERTY.equals(propertyName)) {
addValueChangeListener(listener);
}
}
public void removePropertyChangeListener(PropertyChangeListener listener) {
throw new UnsupportedOperationException("not implemented");
}
public void removePropertyChangeListener(String propertyName, PropertyChangeListener listener) {
if (DIRTY_PROPERTY.equals(propertyName)) {
dirtyChangeListeners.remove(listener);
}
else if (VALUE_PROPERTY.equals(propertyName)) {
removeValueChangeListener(listener);
}
}
/**
* Handles the dirty event firing.
*
* @param propertyName implementation only handles DIRTY_PROPERTY.
* @param oldValue
* @param newValue
*/
protected final void firePropertyChange(String propertyName, boolean oldValue, boolean newValue) {
if (DIRTY_PROPERTY.equals(propertyName)) {
PropertyChangeEvent evt = new PropertyChangeEvent(this, propertyName, Boolean.valueOf(oldValue), Boolean
.valueOf(newValue));
for (Iterator i = dirtyChangeListeners.iterator(); i.hasNext();) {
((PropertyChangeListener) i.next()).propertyChange(evt);
}
}
}
}