blob: 2f1684a2e79e223484f983efd4d1c3e5323573db [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2005, 2006 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* Brad Reynolds - bug 159539
* Brad Reynolds - bug 140644
* Brad Reynolds - bug 159940
* Brad Reynolds - bug 116920, 159768
*******************************************************************************/
package org.eclipse.core.databinding;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.observable.Realm;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.WritableList;
import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.internal.databinding.ListBinding;
import org.eclipse.core.internal.databinding.ValidationStatusMap;
import org.eclipse.core.internal.databinding.ValueBinding;
import org.eclipse.core.runtime.Assert;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
/**
* A context for binding observable objects. This class is not intended to be
* subclassed by clients.
*
* <p>
* <strong>EXPERIMENTAL</strong>. This class or interface has been added as
* part of a work in progress. There is no guarantee that this API will remain
* unchanged during the 3.2 release cycle. Please do not use this API without
* consulting with the Platform/UI team.
* </p>
*
* @since 1.0
*
*/
public class DataBindingContext {
private List bindingEventListeners = new ArrayList();
private WritableList bindings;
/**
* Unmodifiable version of {@link #bindings} for public exposure.
*/
private IObservableList unmodifiableBindings;
private IObservableMap validationStatusMap;
private Realm validationRealm;
/**
* Creates a data binding context, using the current default realm for the
* validation observables.
*
* @see Realm
*/
public DataBindingContext() {
this(Realm.getDefault());
}
/**
* Creates a data binding context using the given realm for the validation
* observables.
*
* @param validationRealm
* the realm to be used for the validation observables
*
* @see Realm
*/
public DataBindingContext(Realm validationRealm) {
Assert.isNotNull(validationRealm);
this.validationRealm = validationRealm;
bindings = new WritableList(validationRealm);
unmodifiableBindings = Observables.unmodifiableObservableList(bindings);
validationStatusMap = new ValidationStatusMap(validationRealm,
bindings, false);
}
/**
* Add a listener to the set of listeners that will be notified when an
* event occurs in the data flow pipeline managed by any binding in this
* data binding context.
*
* @param listener
* The listener to add.
*/
public void addBindingEventListener(IBindingListener listener) {
bindingEventListeners.add(listener);
}
/**
* Binds two observable values using converters and validators as specified
* in <code>bindSpec</code>. If <code>bindSpec</code> is <code>null</code>,
* default converters and validators as defined by {@link DefaultBindSpec}
* will be used.
* <p>
* The phases performed for a value binding occur in the following order for
* each event: {@link BindingEvent#EVENT_COPY_TO_MODEL} and
* {@link BindingEvent#EVENT_COPY_TO_TARGET}:
* <ol>
* <li>{@link BindingEvent#PIPELINE_VALUE_CHANGING} (target to model only)</li>
* <li>{@link BindingEvent#PIPELINE_AFTER_GET}</li>
* <li>{@link BindingEvent#PIPELINE_AFTER_CONVERT}</li>
* <li>{@link BindingEvent#PIPELINE_BEFORE_CHANGE}</li>
* <li>{@link BindingEvent#PIPELINE_AFTER_CHANGE}</li>
* </ol>
* Multiple validators are honored for every phase except <code>BindingEvent.PIPELINE_AFTER_CHANGE</code>
* (it doesn't make sense to validate after the change is applied).
* Validators will be invoked in the order that they are added and a failure
* in validation will terminate pipeline processing. The provided validation
* status will be propagated to {@link Binding#getValidationStatus()} and
* {@link #getValidationStatusMap()}.
* </p>
* <p>
* All phases perform the duty their name implies except <code>PIPELINE_BEFORE_CHANGE</code>;
* it has no defined role in the pipeline. This phase is provided as a means
* to perform validation during data entry but to defer other validation
* until a copy to the model is to occur. A common use case where this is
* employed is the editing of a model in a dialog. The model should not be
* updated until OK/Apply is selected but common validation (e.g. type and
* range checking) is to be performed as the user is interacting with the
* UI. The following {@link BindSpec} configuration will setup such a use
* case:
*
* <pre>
* <code>
* new DefaultBindSpec().setModelUpdatePolicy(BindSpec.POLICY_EXPLICIT)
* .addTargetValidator(BindingEvent.PIPELINE_BEFORE_CHANGE,
* new Validator());
* </code>
* </pre>
*
* By default with a
* {@link BindSpec#setModelUpdatePolicy(Integer) model update policy} of
* {@link BindSpec#POLICY_EXPLICIT} validation is ran up to and including
* <code>BindingEvent.PIPELINE_AFTER_CONVERT</code> on every change of the
* target. In the above example the provided validator will only be invoked
* on an explicit model updates {@link Binding#updateModelFromTarget()}.
* </p>
*
* @param targetObservableValue
* @param modelObservableValue
* @param bindSpec
* the bind spec, or null. A bindSpec object must not be reused
* or changed after it is passed to this method.
* @return a Binding synchronizing the state of the two observables
*/
public Binding bindValue(IObservableValue targetObservableValue,
IObservableValue modelObservableValue, BindSpec bindSpec) {
if (bindSpec == null) {
bindSpec = new DefaultBindSpec();
}
Binding result = new ValueBinding(targetObservableValue,
modelObservableValue, bindSpec);
result.init(this);
return result;
}
/**
* Binds two observable lists using converter and validator as specified in
* bindSpec. If bindSpec is null, default converters and validators as
* defined by {@link DefaultBindSpec} will be used.
* <p>
* The phases performed for a list binding occur in the following order for
* each event: {@link BindingEvent#EVENT_COPY_TO_MODEL} and
* {@link BindingEvent#EVENT_COPY_TO_TARGET}:
* <ol>
* <li>{@link BindingEvent#PIPELINE_AFTER_GET}</li>
* <li>{@link BindingEvent#PIPELINE_BEFORE_CHANGE}</li>
* <li>{@link BindingEvent#PIPELINE_AFTER_CHANGE}</li>
* </ol>
* Multiple validators are honored for every phase except
* <code>BindingEvent.PIPELINE_AFTER_CHANGE</code> (it doesn't make sense
* to validate after the change is applied). Validators will be invoked in
* the order that they are added and a failure in validation will terminate
* pipeline processing. The provided validation status will be propagated to
* {@link Binding#getValidationStatus()} and
* {@link #getValidationStatusMap()}.
* </p>
* <p>
* All phases perform the duty their name implies except
* <code>PIPELINE_BEFORE_CHANGE</code>; it has no defined role in the
* pipeline. This phase is provided as a means to perform validation during
* data entry but to defer other validation until a copy to the model is to
* occur. A common use case where this is employed is the editing of a model
* in a dialog. The model should not be updated until OK/Apply is selected
* but common validation (e.g. type and range checking) is to be performed
* as the user is interacting with the UI. The following {@link BindSpec}
* configuration will setup such a use case:
*
* <pre>
* <code>
* new DefaultBindSpec().setModelUpdatePolicy(BindSpec.POLICY_EXPLICIT)
* .addTargetValidator(BindingEvent.PIPELINE_BEFORE_CHANGE,
* new Validator());
* </code>
* </pre>
* </p>
*
* @param targetObservableList
* @param modelObservableList
* @param bindSpec
* the bind spec, or null. A bindSpec object must not be reused
* or changed after it is passed to this method.
* @return a Binding synchronizing the state of the two observables
*/
public Binding bindList(IObservableList targetObservableList,
IObservableList modelObservableList, BindSpec bindSpec) {
if (bindSpec == null) {
bindSpec = new DefaultBindSpec();
}
Binding result = new ListBinding(targetObservableList,
modelObservableList, bindSpec);
result.init(this);
return result;
}
/**
* Disposes of this data binding context and all bindings that were added to
* this context.
*/
public void dispose() {
Binding[] bindingArray = (Binding[]) bindings.toArray(new Binding[bindings.size()]);
for (int i = 0; i < bindingArray.length; i++) {
bindingArray[i].dispose();
}
}
/**
* Fires a binding event to all binding listeners. To be called by bindings
* in this data binding context.
*
* @param event
* @return a status object
*/
protected IStatus fireBindingEvent(BindingEvent event) {
IStatus result = Status.OK_STATUS;
for (Iterator bindingEventIter = bindingEventListeners.iterator(); bindingEventIter
.hasNext();) {
IBindingListener listener = (IBindingListener) bindingEventIter
.next();
result = listener.handleBindingEvent(event);
if (!result.isOK())
break;
}
return result;
}
/**
* Returns an unmodifiable observable list with elements of type {@link Binding},
* ordered by time of addition.
*
* @return the observable list containing all bindings
*/
public IObservableList getBindings() {
return unmodifiableBindings;
}
/**
* Returns an observable map from bindings (type: {@link Binding}) to
* statuses (type: {@link IStatus}). The keys of the map are the bindings
* returned by {@link #getBindings()}, and the values are the current
* validaion status objects for each binding.
*
* @return the observable map from bindings to status objects.
*/
public IObservableMap getValidationStatusMap() {
return validationStatusMap;
}
/**
* Adds the given binding to this data binding context.
*
* @param binding
* The binding to add.
*/
/* package */ void addBinding(Binding binding) {
bindings.add(binding);
}
/**
* Removes a listener from the set of listeners that will be notified when
* an event occurs in the data flow pipeline that is managed by any binding
* created by this data binding context.
*
* @param listener
* The listener to remove.
*/
public void removeBindingEventListener(IBindingListener listener) {
bindingEventListeners.remove(listener);
}
/**
* Updates all model observable objects to reflect the current state of the
* target observable objects.
*
*/
public void updateModels() {
for (Iterator it = bindings.iterator(); it.hasNext();) {
Binding binding = (Binding) it.next();
binding.updateModelFromTarget();
}
}
/**
* Updates all target observable objects to reflect the current state of the
* model observable objects.
*
*/
public void updateTargets() {
for (Iterator it = bindings.iterator(); it.hasNext();) {
Binding binding = (Binding) it.next();
binding.updateTargetFromModel();
}
}
/**
* Removes the given binding.
*
* @param binding
* @return <code>true</code> if was associated with the context,
* <code>false</code> if not
*/
/* package */ boolean removeBinding(Binding binding) {
return bindings.remove(binding);
}
/**
* Returns the validation realm.
*
* @return the realm for the validation observables
* @see Realm
*/
public Realm getValidationRealm() {
return validationRealm;
}
}