Bug 412499 - inital commit of new Bind methods
diff --git a/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF b/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF
index f2fdfc8..688997f 100644
--- a/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF
+++ b/bundles/org.eclipse.core.databinding/META-INF/MANIFEST.MF
@@ -7,6 +7,7 @@
 Bundle-Vendor: %providerName
 Bundle-Localization: plugin
 Export-Package: org.eclipse.core.databinding,
+ org.eclipse.core.databinding.bind,
  org.eclipse.core.databinding.conversion;x-internal:=false,
  org.eclipse.core.databinding.validation;x-internal:=false,
  org.eclipse.core.internal.databinding;x-friends:="org.eclipse.core.databinding.beans",
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/Bind.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/Bind.java
new file mode 100644
index 0000000..d4ac643
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/Bind.java
@@ -0,0 +1,176 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * @since 1.5
+ * 
+ */
+public class Bind {
+
+	/**
+	 * This is the ITwoWayBinding that sits immediately on top on the model
+	 * observable.
+	 * 
+	 * @param <V>
+	 */
+	static class TwoWayModelBinding<V> extends TwoWayBinding<V> {
+		private final IObservableValue<V> modelObservable;
+
+		private boolean isModelChanging = false;
+
+		IValueChangeListener<V> modelListener = new IValueChangeListener<V>() {
+			public void handleValueChange(ValueChangeEvent<V> event) {
+				if (!isModelChanging) {
+					targetBinding.setTargetValue(event.diff.getNewValue());
+				}
+			}
+		};
+
+		public TwoWayModelBinding(IObservableValue<V> modelObservable,
+				boolean pullInitialValue) {
+			super(pullInitialValue);
+			this.modelObservable = modelObservable;
+
+			modelObservable.addValueChangeListener(modelListener);
+		}
+
+		public V getModelValue() {
+			return modelObservable.getValue();
+		}
+
+		public void setModelValue(V newValue) {
+			isModelChanging = true;
+			try {
+				modelObservable.setValue(newValue);
+			} finally {
+				isModelChanging = false;
+			}
+		}
+
+		public void removeModelListener() {
+			// Actually we don't own this observable so don't dispose,
+			// just remove our listener.
+			modelObservable.removeValueChangeListener(modelListener);
+		}
+	}
+
+	/**
+	 * This is the IOneWayBinding that sits immediately on top on the model
+	 * observable.
+	 * 
+	 * @param <V>
+	 */
+	static class OneWayModelBinding<V> extends OneWayBinding<V> {
+		private final IObservableValue<V> modelObservable;
+
+		IValueChangeListener<V> modelListener = new IValueChangeListener<V>() {
+			public void handleValueChange(ValueChangeEvent<V> event) {
+				targetBinding.setTargetValue(event.diff.getNewValue());
+			}
+		};
+
+		public OneWayModelBinding(IObservableValue<V> modelObservable) {
+			this.modelObservable = modelObservable;
+
+			modelObservable.addValueChangeListener(modelListener);
+		}
+
+		public V getModelValue() {
+			return modelObservable.getValue();
+		}
+
+		public void removeModelListener() {
+			// Actually we don't own this observable so don't dispose,
+			// just remove our listener.
+			modelObservable.removeValueChangeListener(modelListener);
+		}
+	}
+
+	/**
+	 * @param modelObservable
+	 * @return an object that can chain one-way bindings
+	 */
+	public static <V> IOneWayBinding<V> oneWay(
+			IObservableValue<V> modelObservable) {
+		return new OneWayModelBinding<V>(modelObservable);
+	}
+
+	/**
+	 * @param modelObservable
+	 * @return an object that can chain two-way bindings
+	 */
+	public static <V> ITwoWayBinding<V> twoWay(
+			final IObservableValue<V> modelObservable) {
+		return new TwoWayModelBinding<V>(modelObservable, true);
+	}
+
+	/**
+	 * This method is used to 'bounce back' a value from the target.
+	 * Specifically this means whenever the target value changes, the value is
+	 * converted using the given converter (targetToModel). The resulting value
+	 * is the converted back using the same converter (modelToTarget).
+	 * <P>
+	 * A use case for this method is as follows. You have a number that is
+	 * stored in the model as an Integer. You want to display the number in a
+	 * text box with separators, so 1234567 would be displayed as 1,234,567 or
+	 * 1.234.567 depending on your regional settings. You want to allow more
+	 * flexibility on what the user can enter. For example you may want to allow
+	 * the user to miss out the separators. As the user types, the value is
+	 * updated in the model. When the control loses focus you want the
+	 * separators to be inserted in the Text control in the proper positions.
+	 * <P>
+	 * To do this you create two bindings. One is a two-way binding that
+	 * observes the Text control with SWT.Modify. The other is a 'bounce back'
+	 * that observes the control with SWT.FocusOut. It might be coded as
+	 * follows:
+	 * <P>
+	 * <code>
+	 * 		Bind.bounceBack(myIntegerToTextConverter)
+		.to(SWTObservables.observeText(textControl, SWT.FocusOut));
+	 * </code>
+	 * 
+	 * @param converter
+	 * @return an object that can chain two-way bindings
+	 */
+	public static <T1, T2> ITwoWayBinding<T2> bounceBack(
+			final IBidiConverter<T1, T2> converter) {
+		return new TwoWayBinding<T2>(false) {
+
+			public T2 getModelValue() {
+				/*
+				 * This method should never be called because pullInitialValue
+				 * is set to false.
+				 */
+				throw new UnsupportedOperationException();
+			}
+
+			public void setModelValue(T2 valueFromTarget) {
+				try {
+					T1 modelSideValue = converter
+							.targetToModel(valueFromTarget);
+					T2 valueBackToTarget = converter
+							.modelToTarget(modelSideValue);
+					this.targetBinding.setTargetValue(valueBackToTarget);
+				} catch (CoreException e) {
+					/*
+					 * No bounce-back occurs if the value from the target side
+					 * cannot be converted. We do nothing because the user will
+					 * typically have an error indicator anyway.
+					 */
+				}
+			}
+
+			public void removeModelListener() {
+				/*
+				 * Nothing to do here because nothing originates from the model
+				 * side.
+				 */
+			}
+		};
+	}
+
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/DefaultValueBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/DefaultValueBinding.java
new file mode 100644
index 0000000..f5dacf4
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/DefaultValueBinding.java
@@ -0,0 +1,76 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T1>
+ */
+public class DefaultValueBinding<T1> extends TwoWayBinding<T1> implements
+		ITargetBinding<T1> {
+
+	private final IOneWayModelBinding<T1> modelBinding;
+
+	private boolean stopped = false;
+
+	/**
+	 * @param modelBinding
+	 */
+	public DefaultValueBinding(IOneWayModelBinding<T1> modelBinding) {
+		super(true);
+		this.modelBinding = modelBinding;
+	}
+
+	public T1 getModelValue() {
+		return modelBinding.getModelValue();
+	}
+
+	public void setTargetValue(T1 valueOnModelSide) {
+		targetBinding.setTargetValue(valueOnModelSide);
+	}
+
+	public void setStatus(IStatus status) {
+		/*
+		 * Generally there are no status values sent from the model to the
+		 * target because the model is generally valid. However there may be
+		 * cases when error or warning statuses come from the model. For example
+		 * when using JSR-303 validations the validation is done on the value in
+		 * the model object. In any case we just pass it on.
+		 */
+		targetBinding.setStatus(status);
+	}
+
+	/*
+	 * (non-Javadoc)
+	 * 
+	 * @see
+	 * org.eclipse.core.databinding.bind.IModelBinding#setModelValue(java.lang
+	 * .Object)
+	 */
+	public void setModelValue(T1 newValue) {
+		/*
+		 * The target has changed so stop this binding. The target will continue
+		 * to notify us of changes for as long as it exists so we need to set a
+		 * flag to indicate that this binding is in a stopped state.
+		 */
+		if (!stopped) {
+			stopped = true;
+			modelBinding.removeModelListener();
+		}
+	}
+
+	public void removeModelListener() {
+		/*
+		 * Pass the request back to the next link in the binding chain so
+		 * eventually the request gets back to the model observable.
+		 * 
+		 * Note that if we are in a 'stopped' state then the listener has
+		 * already been removed from the model and we should not attempt to
+		 * remove it again.
+		 */
+		if (!stopped) {
+			modelBinding.removeModelListener();
+		}
+	}
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IBidiConverter.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IBidiConverter.java
new file mode 100644
index 0000000..183ca3b
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IBidiConverter.java
@@ -0,0 +1,27 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.runtime.CoreException;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T1>
+ * @param <T2>
+ */
+public interface IBidiConverter<T1, T2> {
+
+	/**
+	 * @param fromObject
+	 * @return the value converted for use on the target side
+	 */
+	T2 modelToTarget(T1 fromObject);
+
+	/**
+	 * @param fromObject
+	 * @return the value converted for use on the model side
+	 * @throws CoreException
+	 *             if the value cannot be converted
+	 */
+	T1 targetToModel(T2 fromObject) throws CoreException;
+
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IModelBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IModelBinding.java
new file mode 100644
index 0000000..0f03e74
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IModelBinding.java
@@ -0,0 +1,25 @@
+package org.eclipse.core.databinding.bind;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T>
+ */
+public interface IModelBinding<T> {
+
+	/**
+	 * @return the model from the model side
+	 */
+	T getModelValue();
+
+	/**
+	 * @param newValue
+	 */
+	void setModelValue(T newValue);
+
+	/**
+	 * Removes the listener from the model. This method is called when the
+	 * target is disposed.
+	 */
+	void removeModelListener();
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayBinding.java
new file mode 100644
index 0000000..eeac057
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayBinding.java
@@ -0,0 +1,62 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.conversion.IConverter;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T1>
+ */
+public interface IOneWayBinding<T1> {
+
+	/**
+	 * @param converter
+	 * @return the value converted to the type expected by the next part of the
+	 *         binding chain
+	 */
+	<T2> IOneWayBinding<T2> convert(IConverter<T1, T2> converter);
+
+	/**
+	 * This method is similar to <code>convert</code>. However if any
+	 * observables are read during the conversion then listeners are added to
+	 * these observables and the conversion is done again.
+	 * <P>
+	 * The conversion is always repeated keeping the same value of the model. It
+	 * is assumed that the tracked observables affect the target. For example
+	 * suppose a time widget contains a time which is bound to a Date property
+	 * in the model. The time zone to use is a preference and an observable
+	 * exists for the time zone (which would implement
+	 * IObservableValue<TimeZone>). If the user changes the time zone in the
+	 * preferences then the text in the time widget will change to show the same
+	 * time but in a different time zone. The time in the model will not change
+	 * when the time zone is changed. If the user edits the time in the time
+	 * widget then that time will be interpreted using the new time zone and
+	 * converted to a Date object for the model.
+	 * 
+	 * @param converter
+	 * @return an object that can chain one-way bindings
+	 */
+	<T2> IOneWayBinding<T2> convertWithTracking(IConverter<T1, T2> converter);
+
+	/**
+	 * @param targetObservable
+	 */
+	void to(IObservableValue<T1> targetObservable);
+
+	/**
+	 * This method is used to create a one-way binding from the model to the
+	 * target but the binding stops if something else changes the target.
+	 * <P>
+	 * A use case is when a default value is provided in a UI control. For
+	 * example suppose you have two fields, 'amount' and 'sales tax'. When the
+	 * user enters an amount, the requirement is that the 'sales tax' field is
+	 * completed based on the amount using a given tax rate. If the user edits
+	 * the amount, the sales tax amount changes accordingly. However once the
+	 * user edits the sales tax field then changes to the amount field no longer
+	 * affect the sales tax field.
+	 * 
+	 * @return an object that can chain two-way bindings
+	 */
+	ITwoWayBinding<T1> untilTargetChanges();
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayModelBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayModelBinding.java
new file mode 100644
index 0000000..98be6c7
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/IOneWayModelBinding.java
@@ -0,0 +1,20 @@
+package org.eclipse.core.databinding.bind;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T>
+ */
+public interface IOneWayModelBinding<T> {
+
+	/**
+	 * @return the value from the model side
+	 */
+	T getModelValue();
+
+	/**
+	 * Removes the listener from the model. This method is called when the
+	 * target is disposed.
+	 */
+	void removeModelListener();
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITargetBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITargetBinding.java
new file mode 100644
index 0000000..3fc6d6c
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITargetBinding.java
@@ -0,0 +1,22 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ * 
+ * @param <F>
+ */
+public interface ITargetBinding<F> {
+	/**
+	 * @param targetValue
+	 */
+	void setTargetValue(F targetValue);
+
+	/**
+	 * Push the error status back to the target
+	 * 
+	 * @param status
+	 */
+	void setStatus(IStatus status);
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITwoWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITwoWayBinding.java
new file mode 100644
index 0000000..124066e
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/ITwoWayBinding.java
@@ -0,0 +1,43 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+
+/**
+ * @since 1.5
+ * @param <T1>
+ */
+public interface ITwoWayBinding<T1> {
+
+	/**
+	 * @param converter
+	 * @return an object that can chain two-way bindings
+	 */
+	<T2> ITwoWayBinding<T2> convert(final IBidiConverter<T1, T2> converter);
+
+	/**
+	 * This method is similar to <code>convert</code>. However if any
+	 * observables are read during the conversion then listeners are added to
+	 * these observables and the conversion is done again.
+	 * <P>
+	 * The conversion is always repeated keeping the same value of the model. It
+	 * is assumed that the tracked observables affect the target. For example
+	 * suppose a time widget contains a time which is bound to a Date property
+	 * in the model. The time zone to use is a preference and an observable
+	 * exists for the time zone (which would implement
+	 * IObservableValue<TimeZone>). If the user changes the time zone in the
+	 * preferences then the text in the time widget will change to show the same
+	 * time but in a different time zone. The time in the model will not change
+	 * when the time zone is changed. If the user edits the time in the time
+	 * widget then that time will be interpreted using the new time zone and
+	 * converted to a Date object for the model.
+	 * 
+	 * @param converter
+	 * @return an object that can chain two-way bindings
+	 */
+	<T2> ITwoWayBinding<T2> convertWithTracking(IBidiConverter<T1, T2> converter);
+
+	/**
+	 * @param targetObservable
+	 */
+	void to(IObservableValue<T1> targetObservable);
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayBinding.java
new file mode 100644
index 0000000..8c82d1a
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayBinding.java
@@ -0,0 +1,149 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.conversion.IConverter;
+import org.eclipse.core.databinding.observable.DisposeEvent;
+import org.eclipse.core.databinding.observable.IDisposeListener;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T2>
+ */
+public abstract class OneWayBinding<T2> implements IOneWayBinding<T2>,
+		IOneWayModelBinding<T2> {
+
+	protected ITargetBinding<T2> targetBinding;
+
+	public <T3> IOneWayBinding<T3> convert(final IConverter<T2, T3> converter) {
+		if (targetBinding != null) {
+			throw new RuntimeException(
+					"When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+		}
+
+		OneWayConversionBinding<T3, T2> nextBinding = new OneWayConversionBinding<T3, T2>(
+				this, converter);
+		targetBinding = nextBinding;
+		return nextBinding;
+	}
+
+	/**
+	 * This method is similar to <code>convert</code>. However if any
+	 * observables are read during the conversion then listeners are added to
+	 * these observables and the conversion is done again.
+	 * <P>
+	 * The conversion is always repeated keeping the same value of the model. It
+	 * is assumed that the tracked observables affect the target. For example
+	 * suppose a time widget contains a time which is bound to a Date property
+	 * in the model. The time zone to use is a preference and an observable
+	 * exists for the time zone (which would implement
+	 * IObservableValue<TimeZone>). If the user changes the time zone in the
+	 * preferences then the text in the time widget will change to show the same
+	 * time but in a different time zone. The time in the model will not change
+	 * when the time zone is changed. If the user edits the time in the time
+	 * widget then that time will be interpreted using the new time zone and
+	 * converted to a Date object for the model.
+	 * 
+	 * @param converter
+	 * @return an object that can chain one-way bindings
+	 */
+	public <T3> IOneWayBinding<T3> convertWithTracking(
+			final IConverter<T2, T3> converter) {
+		if (targetBinding != null) {
+			throw new RuntimeException(
+					"When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+		}
+
+		OneWayConversionBinding<T3, T2> nextBinding = new OneWayConversionBinding<T3, T2>(
+				this, converter);
+		targetBinding = nextBinding;
+		return nextBinding;
+	}
+
+	/**
+	 * This method creates a binding where
+	 * <P>
+	 * This method is used to provide a default value. The default value is a
+	 * one-way binding, typically a one-way binding from a ComputedValue. This
+	 * default value is the receiver of this method. The target may be either an
+	 * observable on the model or an observable on a UI control. (If you have
+	 * two-way binding between the model and the UI control then you can bind a
+	 * default value to either but you will typically have fewer conversions to
+	 * do if you bind to the model).
+	 * <P>
+	 * The binding on to the target is a two-way binding. The default value is
+	 * passed on to the target until such time that the target is changed by
+	 * someone other than ourselves. At that point the binding stops and changes
+	 * in the default value are no longer set into the target.
+	 * <P>
+	 * An example use is as follows: There is a field in which the user can
+	 * enter the price of an item. There is another field in which the user can
+	 * enter the sales tax for the sale of the item. We want the sales tax field
+	 * to automatically calculate to be 5% of the price. If the user edits the
+	 * price then the sales tax field should change too. The user may edit the
+	 * sales tax field. If the user does this then the sales tax field will no
+	 * longer be updated as the price changes.
+	 */
+	public ITwoWayBinding<T2> untilTargetChanges() {
+		if (targetBinding != null) {
+			throw new RuntimeException(
+					"When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+		}
+
+		DefaultValueBinding<T2> nextBinding = new DefaultValueBinding<T2>(this);
+
+		targetBinding = nextBinding;
+		return nextBinding;
+	}
+
+	public void to(final IObservableValue<T2> targetObservable) {
+		/*
+		 * We have finally made it to the target observable.
+		 * 
+		 * Initially set the target observable to the current value from the
+		 * model.
+		 */
+		targetObservable.setValue(getModelValue());
+
+		/*
+		 * The target binding contains a method that is called whenever a new
+		 * value comes from the model side. We simply set a target binding that
+		 * sets that value into the target observable.
+		 */
+		targetBinding = new ITargetBinding<T2>() {
+			public void setTargetValue(T2 targetValue) {
+				targetObservable.setValue(targetValue);
+			}
+
+			public void setStatus(IStatus status) {
+				/*
+				 * Generally there are no status values sent from the model to
+				 * the target because the model is generally valid. However
+				 * there may be cases when error or warning statuses come from
+				 * the model. For example when using JSR-303 validations the
+				 * validation is done on the value in the model object. In any
+				 * case, there is no status observable provided by the user so
+				 * we drop it.
+				 */
+			}
+		};
+
+		/*
+		 * If the target is disposed, be sure to remove the listener from the
+		 * model.
+		 */
+		targetObservable.addDisposeListener(new IDisposeListener() {
+			public void handleDispose(DisposeEvent event) {
+				removeModelListener();
+			}
+		});
+	}
+
+	/**
+	 * @param status
+	 */
+	public void setStatus(IStatus status) {
+		targetBinding.setStatus(status);
+	}
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayConversionBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayConversionBinding.java
new file mode 100644
index 0000000..f5fe201
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/OneWayConversionBinding.java
@@ -0,0 +1,43 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.conversion.IConverter;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T2>
+ * @param <T1>
+ */
+public class OneWayConversionBinding<T2, T1> extends OneWayBinding<T2>
+		implements ITargetBinding<T1> {
+	private final IOneWayModelBinding<T1> modelBinding;
+	private final IConverter<T1, T2> converter;
+
+	/**
+	 * @param modelBinding
+	 * @param converter
+	 */
+	public OneWayConversionBinding(IOneWayModelBinding<T1> modelBinding,
+			IConverter<T1, T2> converter) {
+		this.modelBinding = modelBinding;
+		this.converter = converter;
+	}
+
+	public T2 getModelValue() {
+		T1 modelValue = modelBinding.getModelValue();
+		return converter.convert(modelValue);
+	}
+
+	public void setTargetValue(T1 valueOnModelSide) {
+		T2 valueOnTargetSide = converter.convert(valueOnModelSide);
+		targetBinding.setTargetValue(valueOnTargetSide);
+	}
+
+	public void removeModelListener() {
+		/*
+		 * Pass the request back to the next link in the binding chain so
+		 * eventually the request gets back to the model observable.
+		 */
+		modelBinding.removeModelListener();
+	}
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayBinding.java
new file mode 100644
index 0000000..f7860ac
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayBinding.java
@@ -0,0 +1,167 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.observable.DisposeEvent;
+import org.eclipse.core.databinding.observable.IDisposeListener;
+import org.eclipse.core.databinding.observable.value.IObservableValue;
+import org.eclipse.core.databinding.observable.value.IValueChangeListener;
+import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
+import org.eclipse.core.databinding.validation.IValidator;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T2>
+ */
+public abstract class TwoWayBinding<T2> implements ITwoWayBinding<T2>,
+		IModelBinding<T2> {
+
+	/**
+	 * <code>true</code> if the target observable bound by the <code>to</code>
+	 * method is to be initially set to the value from the model side,
+	 * <code>false</code> if its value is to be set only when changes are pushed
+	 * from the target side
+	 */
+	protected boolean pullInitialValue;
+
+	protected ITargetBinding<T2> targetBinding;
+
+	/**
+	 * @param pullInitialValue
+	 */
+	public TwoWayBinding(boolean pullInitialValue) {
+		this.pullInitialValue = pullInitialValue;
+	}
+
+	public <T3> ITwoWayBinding<T3> convert(
+			final IBidiConverter<T2, T3> converter) {
+		if (targetBinding != null) {
+			throw new RuntimeException(
+					"When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+		}
+
+		TwoWayConversionBinding<T3, T2> nextBinding = new TwoWayConversionBinding<T3, T2>(
+				this, converter, pullInitialValue);
+		targetBinding = nextBinding;
+		return nextBinding;
+	}
+
+	/**
+	 * This method is similar to <code>convert</code>. However if any
+	 * observables are read during the conversion then listeners are added to
+	 * these observables and the conversion is done again.
+	 * <P>
+	 * The conversion is always repeated keeping the same value of the model. It
+	 * is assumed that the tracked observables affect the target. For example
+	 * suppose a time widget contains a time which is bound to a Date property
+	 * in the model. The time zone to use is a preference and an observable
+	 * exists for the time zone (which would implement
+	 * IObservableValue<TimeZone>). If the user changes the time zone in the
+	 * preferences then the text in the time widget will change to show the same
+	 * time but in a different time zone. The time in the model will not change
+	 * when the time zone is changed. If the user edits the time in the time
+	 * widget then that time will be interpreted using the new time zone and
+	 * converted to a Date object for the model.
+	 * 
+	 * @param converter
+	 * @return an object that can chain two-way bindings
+	 */
+	public <T3> ITwoWayBinding<T3> convertWithTracking(
+			final IBidiConverter<T2, T3> converter) {
+		if (targetBinding != null) {
+			throw new RuntimeException(
+					"When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+		}
+
+		TwoWayConversionBinding<T3, T2> nextBinding = new TwoWayConversionBinding<T3, T2>(
+				this, converter, pullInitialValue);
+		targetBinding = nextBinding;
+		return nextBinding;
+	}
+
+	/**
+	 * @param validator
+	 * @return an object that can chain two-way bindings
+	 */
+	public ITwoWayBinding<T2> validate(final IValidator<T2> validator) {
+		if (targetBinding != null) {
+			throw new RuntimeException(
+					"When chaining together a binding, you cannot chain more than one target."); //$NON-NLS-1$
+		}
+
+		TwoWayValidationBinding<T2> nextBinding = new TwoWayValidationBinding<T2>(
+				this, validator, pullInitialValue);
+		targetBinding = nextBinding;
+		return nextBinding;
+	}
+
+	public void to(final IObservableValue<T2> targetObservable) {
+		to(targetObservable, null);
+	}
+
+	/**
+	 * We have finally made it to the target observable.
+	 * 
+	 * Initially set the target observable to the current value from the model
+	 * (if the pullInitialValue flag is set which it will be in most cases).
+	 * 
+	 * @param targetObservable
+	 * @param statusObservable
+	 */
+	public void to(final IObservableValue<T2> targetObservable,
+			final IObservableValue<IStatus> statusObservable) {
+		if (pullInitialValue) {
+			targetObservable.setValue(getModelValue());
+		}
+
+		final boolean[] isChanging = new boolean[] { false };
+
+		/*
+		 * The target binding contains a method that is called whenever a new
+		 * value comes from the model side. We simply set a target binding that
+		 * sets that value into the target observable.
+		 */
+		targetBinding = new ITargetBinding<T2>() {
+			public void setTargetValue(T2 targetValue) {
+				try {
+					isChanging[0] = true;
+					targetObservable.setValue(targetValue);
+				} finally {
+					isChanging[0] = false;
+				}
+			}
+
+			public void setStatus(IStatus status) {
+				/*
+				 * If there is a target for the status, set it. Otherwise drop
+				 * it.
+				 */
+				if (statusObservable != null) {
+					statusObservable.setValue(status);
+				}
+			}
+		};
+
+		/*
+		 * Listen for changes originating from the target observable, and send
+		 * those back through to the model side.
+		 */
+		targetObservable.addValueChangeListener(new IValueChangeListener<T2>() {
+			public void handleValueChange(ValueChangeEvent<T2> event) {
+				if (!isChanging[0]) {
+					setModelValue(event.diff.getNewValue());
+				}
+			}
+		});
+
+		/*
+		 * If the target is disposed, be sure to remove the listener from the
+		 * model.
+		 */
+		targetObservable.addDisposeListener(new IDisposeListener() {
+			public void handleDispose(DisposeEvent event) {
+				removeModelListener();
+			}
+		});
+	}
+}
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayConversionBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayConversionBinding.java
new file mode 100644
index 0000000..e985692
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayConversionBinding.java
@@ -0,0 +1,71 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.runtime.CoreException;
+import org.eclipse.core.runtime.IStatus;
+import org.eclipse.core.runtime.Status;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T2>
+ * @param <T1>
+ */
+public class TwoWayConversionBinding<T2, T1> extends TwoWayBinding<T2>
+		implements ITargetBinding<T1> {
+	private final IModelBinding<T1> modelBinding;
+	private final IBidiConverter<T1, T2> converter;
+
+	/**
+	 * @param modelBinding
+	 * @param converter
+	 * @param pullInitialValue
+	 */
+	public TwoWayConversionBinding(IModelBinding<T1> modelBinding,
+			IBidiConverter<T1, T2> converter, boolean pullInitialValue) {
+		super(pullInitialValue);
+		this.modelBinding = modelBinding;
+		this.converter = converter;
+	}
+
+	/**
+	 * @return the value from the model, converted for use on the target side
+	 */
+	public T2 getModelValue() {
+		T1 modelValue = modelBinding.getModelValue();
+		return converter.modelToTarget(modelValue);
+	}
+
+	/**
+	 * @param valueOnTargetSide
+	 */
+	public void setModelValue(T2 valueOnTargetSide) {
+		try {
+			T1 valueOnModelSide = converter.targetToModel(valueOnTargetSide);
+			modelBinding.setModelValue(valueOnModelSide);
+			targetBinding.setStatus(Status.OK_STATUS);
+		} catch (CoreException e) {
+			targetBinding.setStatus(e.getStatus());
+		}
+	}
+
+	public void setTargetValue(T1 valueOnModelSide) {
+		T2 valueOnTargetSide = converter.modelToTarget(valueOnModelSide);
+		targetBinding.setTargetValue(valueOnTargetSide);
+	}
+
+	public void setStatus(IStatus status) {
+		/*
+		 * The error occurred somewhere on the model side. We push this error
+		 * back to the target.
+		 */
+		targetBinding.setStatus(status);
+	}
+
+	public void removeModelListener() {
+		/*
+		 * Pass the request back to the next link in the binding chain so
+		 * eventually the request gets back to the model observable.
+		 */
+		modelBinding.removeModelListener();
+	}
+}
\ No newline at end of file
diff --git a/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayValidationBinding.java b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayValidationBinding.java
new file mode 100644
index 0000000..a782110
--- /dev/null
+++ b/bundles/org.eclipse.core.databinding/src/org/eclipse/core/databinding/bind/TwoWayValidationBinding.java
@@ -0,0 +1,80 @@
+package org.eclipse.core.databinding.bind;
+
+import org.eclipse.core.databinding.validation.IValidator;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 1.5
+ * 
+ * @param <T>
+ */
+public class TwoWayValidationBinding<T> extends TwoWayBinding<T> implements
+		ITargetBinding<T> {
+	private final IModelBinding<T> modelBinding;
+	private final IValidator<T> validator;
+
+	/**
+	 * @param modelBinding
+	 * @param validator
+	 * @param pullInitialValue
+	 */
+	public TwoWayValidationBinding(IModelBinding<T> modelBinding,
+			IValidator<T> validator, boolean pullInitialValue) {
+		super(pullInitialValue);
+		this.modelBinding = modelBinding;
+		this.validator = validator;
+	}
+
+	/**
+	 * The default behavior is to validate only when going from target to model.
+	 * The error status observable is assumed to be there to show user errors.
+	 * Therefore no validation is done in this method.
+	 * 
+	 * @return the value from the model
+	 */
+	public T getModelValue() {
+		return modelBinding.getModelValue();
+	}
+
+	/**
+	 * @param valueOnTargetSide
+	 */
+	public void setModelValue(T valueOnTargetSide) {
+		IStatus status = validator.validate(valueOnTargetSide);
+		targetBinding.setStatus(status);
+
+		/*
+		 * We pass on the value towards the model only if a warning or better.
+		 * We block if an error or worse. This may or may not be the behavior
+		 * expected by the users.
+		 */
+		if (status.getSeverity() >= IStatus.WARNING) {
+			modelBinding.setModelValue(valueOnTargetSide);
+		}
+	}
+
+	/**
+	 * The default behavior is to validate only when going from target to model.
+	 * The error status observable is assumed to be there to show user errors.
+	 * Therefore no validation is done in this method.
+	 */
+	public void setTargetValue(T valueOnModelSide) {
+		targetBinding.setTargetValue(valueOnModelSide);
+	}
+
+	public void setStatus(IStatus status) {
+		/*
+		 * The error occurred somewhere on the model side. We push this error
+		 * back to the target.
+		 */
+		targetBinding.setStatus(status);
+	}
+
+	public void removeModelListener() {
+		/*
+		 * Pass the request back to the next link in the binding chain so
+		 * eventually the request gets back to the model observable.
+		 */
+		modelBinding.removeModelListener();
+	}
+}
\ No newline at end of file