blob: 87eebd3ebf47595e832008d8872e0b6498e0e2a0 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007, 2015 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
* Matthew Hall - bugs 220700, 271148, 278550
*******************************************************************************/
package org.eclipse.core.databinding;
import org.eclipse.core.databinding.observable.ObservableTracker;
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.observable.value.WritableValue;
import org.eclipse.core.databinding.util.Policy;
import org.eclipse.core.internal.databinding.BindingStatus;
import org.eclipse.core.internal.databinding.Util;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.MultiStatus;
import org.eclipse.core.runtime.Status;
/**
* @since 1.0
*
*/
class ValueBinding extends Binding {
private final UpdateValueStrategy targetToModel;
private final UpdateValueStrategy modelToTarget;
private WritableValue validationStatusObservable;
private IObservableValue target;
private IObservableValue model;
private boolean updatingTarget;
private boolean updatingModel;
private IValueChangeListener targetChangeListener = new IValueChangeListener() {
@Override
public void handleValueChange(ValueChangeEvent event) {
if (!updatingTarget
&& !Util.equals(event.diff.getOldValue(), event.diff
.getNewValue())) {
doUpdate(target, model, targetToModel, false, false);
}
}
};
private IValueChangeListener modelChangeListener = new IValueChangeListener() {
@Override
public void handleValueChange(ValueChangeEvent event) {
if (!updatingModel
&& !Util.equals(event.diff.getOldValue(), event.diff
.getNewValue())) {
doUpdate(model, target, modelToTarget, false, false);
}
}
};
/**
* @param targetObservableValue
* @param modelObservableValue
* @param targetToModel
* @param modelToTarget
*/
public ValueBinding(IObservableValue targetObservableValue,
IObservableValue modelObservableValue,
UpdateValueStrategy targetToModel, UpdateValueStrategy modelToTarget) {
super(targetObservableValue, modelObservableValue);
this.target = targetObservableValue;
this.model = modelObservableValue;
this.targetToModel = targetToModel;
this.modelToTarget = modelToTarget;
}
@Override
protected void preInit() {
ObservableTracker.setIgnore(true);
try {
validationStatusObservable = new WritableValue(context
.getValidationRealm(), Status.OK_STATUS, IStatus.class);
} finally {
ObservableTracker.setIgnore(false);
}
}
@Override
protected void postInit() {
if (modelToTarget.getUpdatePolicy() == UpdateValueStrategy.POLICY_UPDATE) {
model.addValueChangeListener(modelChangeListener);
updateModelToTarget();
} else if (modelToTarget.getUpdatePolicy() == UpdateValueStrategy.POLICY_CONVERT) {
model.addValueChangeListener(modelChangeListener);
validateModelToTarget();
} else {
modelChangeListener = null;
}
if (targetToModel.getUpdatePolicy() == UpdateValueStrategy.POLICY_UPDATE) {
target.addValueChangeListener(targetChangeListener);
if (modelToTarget.getUpdatePolicy() == UpdateValueStrategy.POLICY_NEVER) {
updateTargetToModel();
} else {
validateTargetToModel();
}
} else if (targetToModel.getUpdatePolicy() == UpdateValueStrategy.POLICY_CONVERT) {
target.addValueChangeListener(targetChangeListener);
validateTargetToModel();
} else {
targetChangeListener = null;
}
}
@Override
public IObservableValue getValidationStatus() {
return validationStatusObservable;
}
@Override
public void updateTargetToModel() {
doUpdate(target, model, targetToModel, true, false);
}
@Override
public void updateModelToTarget() {
doUpdate(model, target, modelToTarget, true, false);
}
/**
* Incorporates the provided <code>newStats</code> into the
* <code>multieStatus</code>.
*
* @param multiStatus
* @param newStatus
* @return <code>true</code> if the update should proceed
*/
/* package */boolean mergeStatus(MultiStatus multiStatus, IStatus newStatus) {
if (!newStatus.isOK()) {
multiStatus.add(newStatus);
return multiStatus.getSeverity() < IStatus.ERROR;
}
return true;
}
/*
* This method may be moved to UpdateValueStrategy in the future if clients
* need more control over how the source value is copied to the destination
* observable.
*/
private void doUpdate(final IObservableValue source,
final IObservableValue destination,
final UpdateValueStrategy updateValueStrategy,
final boolean explicit, final boolean validateOnly) {
final int policy = updateValueStrategy.getUpdatePolicy();
if (policy == UpdateValueStrategy.POLICY_NEVER)
return;
if (policy == UpdateValueStrategy.POLICY_ON_REQUEST && !explicit)
return;
execAfterDisposalCheck(source, () -> {
boolean destinationRealmReached = false;
final MultiStatus multiStatus = BindingStatus.ok();
try {
// Get value
Object value = source.getValue();
// Validate after get
IStatus status = updateValueStrategy.validateAfterGet(value);
if (!mergeStatus(multiStatus, status))
return;
// Convert value
final Object convertedValue = updateValueStrategy.convert(value);
// Validate after convert
status = updateValueStrategy.validateAfterConvert(convertedValue);
if (!mergeStatus(multiStatus, status))
return;
if (policy == UpdateValueStrategy.POLICY_CONVERT && !explicit)
return;
// Validate before set
status = updateValueStrategy.validateBeforeSet(convertedValue);
if (!mergeStatus(multiStatus, status))
return;
if (validateOnly)
return;
// Set value
destinationRealmReached = true;
execAfterDisposalCheck(destination, () -> {
if (destination == target) {
updatingTarget = true;
} else {
updatingModel = true;
}
try {
IStatus setterStatus = updateValueStrategy.doSet(destination, convertedValue);
mergeStatus(multiStatus, setterStatus);
} finally {
if (destination == target) {
updatingTarget = false;
} else {
updatingModel = false;
}
setValidationStatus(multiStatus);
}
});
} catch (Exception ex) {
// This check is necessary as in 3.2.2 Status
// doesn't accept a null message (bug 177264).
String message = (ex.getMessage() != null) ? ex.getMessage() : ""; //$NON-NLS-1$
mergeStatus(multiStatus,
new Status(IStatus.ERROR, Policy.JFACE_DATABINDING, IStatus.ERROR, message, ex));
} finally {
if (!destinationRealmReached) {
setValidationStatus(multiStatus);
}
}
});
}
@Override
public void validateModelToTarget() {
doUpdate(model, target, modelToTarget, true, true);
}
@Override
public void validateTargetToModel() {
doUpdate(target, model, targetToModel, true, true);
}
private void setValidationStatus(final IStatus status) {
validationStatusObservable.getRealm().exec(() -> validationStatusObservable.setValue(status));
}
@Override
public void dispose() {
if (targetChangeListener != null) {
target.removeValueChangeListener(targetChangeListener);
targetChangeListener = null;
}
if (modelChangeListener != null) {
model.removeValueChangeListener(modelChangeListener);
modelChangeListener = null;
}
target = null;
model = null;
super.dispose();
}
}