blob: de03bcbb06e185efb918a2bdc608c1ce6d932390 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2007 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
* Matt Carter - Character support completed (bug 197679)
******************************************************************************/
package org.eclipse.core.databinding;
import java.util.Date;
import java.util.HashMap;
import org.eclipse.core.databinding.conversion.IConverter;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.validation.IValidator;
import org.eclipse.core.databinding.validation.ValidationStatus;
import org.eclipse.core.internal.databinding.BindingMessages;
import org.eclipse.core.internal.databinding.Pair;
import org.eclipse.core.internal.databinding.conversion.NumberToBigDecimalConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToBigIntegerConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToByteConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToDoubleConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToFloatConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToIntegerConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToLongConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToNumberConverter;
import org.eclipse.core.internal.databinding.conversion.NumberToShortConverter;
import org.eclipse.core.internal.databinding.conversion.StringToCharacterConverter;
import org.eclipse.core.internal.databinding.conversion.StringToDateConverter;
import org.eclipse.core.internal.databinding.validation.NumberFormatConverter;
import org.eclipse.core.internal.databinding.validation.NumberToByteValidator;
import org.eclipse.core.internal.databinding.validation.NumberToDoubleValidator;
import org.eclipse.core.internal.databinding.validation.NumberToFloatValidator;
import org.eclipse.core.internal.databinding.validation.NumberToIntegerValidator;
import org.eclipse.core.internal.databinding.validation.NumberToLongValidator;
import org.eclipse.core.internal.databinding.validation.NumberToShortValidator;
import org.eclipse.core.internal.databinding.validation.NumberToUnboundedNumberValidator;
import org.eclipse.core.internal.databinding.validation.ObjectToPrimitiveValidator;
import org.eclipse.core.internal.databinding.validation.StringToByteValidator;
import org.eclipse.core.internal.databinding.validation.StringToCharacterValidator;
import org.eclipse.core.internal.databinding.validation.StringToDateValidator;
import org.eclipse.core.internal.databinding.validation.StringToDoubleValidator;
import org.eclipse.core.internal.databinding.validation.StringToFloatValidator;
import org.eclipse.core.internal.databinding.validation.StringToIntegerValidator;
import org.eclipse.core.internal.databinding.validation.StringToLongValidator;
import org.eclipse.core.internal.databinding.validation.StringToShortValidator;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
/**
* Customizes a {@link Binding} between two
* {@link IObservableValue observable values}. The following behaviors can be
* customized via the strategy:
* <ul>
* <li>Validation</li>
* <li>Conversion</li>
* <li>Automatic processing</li>
* </ul>
* <p>
* The update phases are:
* <ol>
* <li>Validate after get - {@link #validateAfterGet(Object)}</li>
* <li>Conversion - {@link #convert(Object)}</li>
* <li>Validate after conversion - {@link #validateAfterConvert(Object)}</li>
* <li>Validate before set - {@link #validateBeforeSet(Object)}</li>
* <li>Value set - {@link #doSet(IObservableValue, Object)}</li>
* </ol>
* </p>
* <p>
* Validation:<br/> {@link IValidator Validators} validate the value at
* multiple phases in the update process. Statuses returned from validators are
* aggregated into a <code>MultiStatus</code> until a status of
* <code>ERROR</code> or <code>CANCEL</code> is encountered. Either of these
* statuses will abort the update process. These statuses are available as the
* {@link Binding#getValidationStatus() binding validation status}.
* </p>
* <p>
* Conversion:<br/> A {@link IConverter converter} will convert the value from
* the type of the source observable into the type of the destination. The
* strategy has the ability to default converters for common scenarios.
* </p>
* <p>
* Automatic processing:<br/> The processing to perform when the source
* observable changes. This behavior is configured via policies provided on
* construction of the strategy (e.g. {@link #POLICY_NEVER},
* {@link #POLICY_CONVERT}, {@link #POLICY_ON_REQUEST}, {@link #POLICY_UPDATE}).
* </p>
*
* @see DataBindingContext#bindValue(IObservableValue, IObservableValue,
* UpdateValueStrategy, UpdateValueStrategy)
* @see Binding#getValidationStatus()
* @see IValidator
* @see IConverter
* @since 1.0
*/
public class UpdateValueStrategy extends UpdateStrategy {
/**
* Policy constant denoting that the source observable's state should not be
* tracked and that the destination observable's value should never be
* updated.
*/
public static int POLICY_NEVER = notInlined(1);
/**
* Policy constant denoting that the source observable's state should not be
* tracked, but that validation, conversion and updating the destination
* observable's value should be performed when explicitly requested.
*/
public static int POLICY_ON_REQUEST = notInlined(2);
/**
* Policy constant denoting that the source observable's state should be
* tracked, including validating changes except for
* {@link #validateBeforeSet(Object)}, but that the destination
* observable's value should only be updated on request.
*/
public static int POLICY_CONVERT = notInlined(4);
/**
* Policy constant denoting that the source observable's state should be
* tracked, and that validation, conversion and updating the destination
* observable's value should be performed automaticlly on every change of
* the source observable value.
*/
public static int POLICY_UPDATE = notInlined(8);
/**
* Helper method allowing API evolution of the above constant values. The
* compiler will not inline constant values into client code if values are
* "computed" using this helper.
*
* @param i
* an integer
* @return the same integer
*/
private static int notInlined(int i) {
return i;
}
protected IValidator afterGetValidator;
protected IValidator afterConvertValidator;
protected IValidator beforeSetValidator;
protected IConverter converter;
private int updatePolicy;
private static ValidatorRegistry validatorRegistry = new ValidatorRegistry();
private static HashMap validatorsByConverter = new HashMap();
protected boolean provideDefaults;
/**
* <code>true</code> if we defaulted the converter
*/
private boolean defaultedConverter = false;
/**
* Creates a new update value strategy for automatically updating the
* destination observable value whenever the source observable value
* changes. Default validators and a default converter will be provided. The
* defaults can be changed by calling one of the setter methods.
*/
public UpdateValueStrategy() {
this(true, POLICY_UPDATE);
}
/**
* Creates a new update value strategy with a configurable update policy.
* Default validators and a default converter will be provided. The defaults
* can be changed by calling one of the setter methods.
*
* @param updatePolicy
* one of {@link #POLICY_NEVER}, {@link #POLICY_ON_REQUEST},
* {@link #POLICY_CONVERT}, or {@link #POLICY_UPDATE}
*/
public UpdateValueStrategy(int updatePolicy) {
this(true, updatePolicy);
}
/**
* Creates a new update value strategy with a configurable update policy.
* Default validators and a default converter will be provided if
* <code>provideDefaults</code> is <code>true</code>. The defaults can
* be changed by calling one of the setter methods.
*
* @param provideDefaults
* if <code>true</code>, default validators and a default
* converter will be provided based on the observable value's
* type.
* @param updatePolicy
* one of {@link #POLICY_NEVER}, {@link #POLICY_ON_REQUEST},
* {@link #POLICY_CONVERT}, or {@link #POLICY_UPDATE}
*/
public UpdateValueStrategy(boolean provideDefaults, int updatePolicy) {
this.provideDefaults = provideDefaults;
this.updatePolicy = updatePolicy;
}
/**
* Converts the value from the source type to the destination type.
* <p>
* Default implementation will use the
* {@link #setConverter(IConverter) converter} if one exists. If no
* converter exists no conversion occurs.
* </p>
*
* @param value
* @return the converted value
*/
public Object convert(Object value) {
return converter == null ? value : converter.convert(value);
}
/**
* Tries to create a validator that can validate values of type fromType.
* Returns <code>null</code> if no validator could be created. Either
* toType or modelDescription can be <code>null</code>, but not both.
*
* @param fromType
* @param toType
* @return an IValidator, or <code>null</code> if unsuccessful
*/
protected IValidator createValidator(Object fromType, Object toType) {
if (fromType == null || toType == null) {
return new IValidator() {
public IStatus validate(Object value) {
return Status.OK_STATUS;
}
};
}
return findValidator(fromType, toType);
}
/**
* Fills out default values based upon the provided <code>source</code>
* and <code>destination</code>. If the strategy is to default values it
* will attempt to default a converter. If the converter can be defaulted an
* attempt is made to default the
* {@link #validateAfterGet(Object) after get validator}. If a validator
* cannot be defaulted it will be <code>null</code>.
*
* @param source
* @param destination
*/
protected void fillDefaults(IObservableValue source,
IObservableValue destination) {
Object sourceType = source.getValueType();
Object destinationType = destination.getValueType();
if (provideDefaults && sourceType != null && destinationType != null) {
if (converter == null) {
IConverter converter = createConverter(sourceType,
destinationType);
defaultedConverter = (converter != null);
setConverter(converter);
}
if (afterGetValidator == null) {
afterGetValidator = createValidator(sourceType, destinationType);
}
}
if (converter != null) {
if (sourceType != null) {
checkAssignable(converter.getFromType(), sourceType,
"converter does not convert from type " + sourceType); //$NON-NLS-1$
}
if (destinationType != null) {
checkAssignable(converter.getToType(), destinationType,
"converter does not convert to type " + destinationType); //$NON-NLS-1$
}
}
}
private IValidator findValidator(Object fromType, Object toType) {
IValidator result = null;
// We only default the validator if we defaulted the converter since the
// two are tightly coupled.
if (defaultedConverter) {
if (String.class.equals(fromType)) {
result = (IValidator) validatorsByConverter.get(converter);
if (result == null) {
// TODO sring based lookup
if (Integer.class.equals(toType)
|| Integer.TYPE.equals(toType)) {
result = new StringToIntegerValidator(
(NumberFormatConverter) converter);
} else if (Long.class.equals(toType)
|| Long.TYPE.equals(toType)) {
result = new StringToLongValidator(
(NumberFormatConverter) converter);
} else if (Float.class.equals(toType)
|| Float.TYPE.equals(toType)) {
result = new StringToFloatValidator(
(NumberFormatConverter) converter);
} else if (Double.class.equals(toType)
|| Double.TYPE.equals(toType)) {
result = new StringToDoubleValidator(
(NumberFormatConverter) converter);
} else if (Byte.class.equals(toType)
|| Byte.TYPE.equals(toType)) {
result = new StringToByteValidator(
(NumberFormatConverter) converter);
} else if (Short.class.equals(toType)
|| Short.TYPE.equals(toType)) {
result = new StringToShortValidator(
(NumberFormatConverter) converter);
} else if (Character.class.equals(toType)
|| Character.TYPE.equals(toType)
&& converter instanceof StringToCharacterConverter) {
result = new StringToCharacterValidator(
(StringToCharacterConverter) converter);
} else if (Date.class.equals(toType)
&& converter instanceof StringToDateConverter) {
result = new StringToDateValidator(
(StringToDateConverter) converter);
}
if (result != null) {
validatorsByConverter.put(converter, result);
}
}
} else if (converter instanceof NumberToNumberConverter) {
result = (IValidator) validatorsByConverter.get(converter);
if (result == null) {
if (converter instanceof NumberToByteConverter) {
result = new NumberToByteValidator(
(NumberToByteConverter) converter);
} else if (converter instanceof NumberToShortConverter) {
result = new NumberToShortValidator(
(NumberToShortConverter) converter);
} else if (converter instanceof NumberToIntegerConverter) {
result = new NumberToIntegerValidator(
(NumberToIntegerConverter) converter);
} else if (converter instanceof NumberToLongConverter) {
result = new NumberToLongValidator(
(NumberToLongConverter) converter);
} else if (converter instanceof NumberToFloatConverter) {
result = new NumberToFloatValidator(
(NumberToFloatConverter) converter);
} else if (converter instanceof NumberToDoubleConverter) {
result = new NumberToDoubleValidator(
(NumberToDoubleConverter) converter);
} else if (converter instanceof NumberToBigIntegerConverter
|| converter instanceof NumberToBigDecimalConverter) {
result = new NumberToUnboundedNumberValidator(
(NumberToNumberConverter) converter);
}
}
}
if (result == null) {
// TODO string based lookup
result = validatorRegistry.get(fromType, toType);
}
}
return result;
}
/**
* @return the update policy
*/
public int getUpdatePolicy() {
return updatePolicy;
}
/**
* Sets the validator to be invoked after the source value is converted to
* the type of the destination observable.
*
* @param validator
* @return the receiver, to enable method call chaining
*/
public UpdateValueStrategy setAfterConvertValidator(IValidator validator) {
this.afterConvertValidator = validator;
return this;
}
/**
* Sets the validator to be invoked after the source value is retrieved at
* the beginning of the synchronization process.
*
* @param validator
* @return the receiver, to enable method call chaining
*/
public UpdateValueStrategy setAfterGetValidator(IValidator validator) {
this.afterGetValidator = validator;
return this;
}
/**
* Sets the validator to be invoked before the value is to be set on the
* destination at the end of the synchronization process.
*
* @param validator
* @return the receiver, to enable method call chaining
*/
public UpdateValueStrategy setBeforeSetValidator(IValidator validator) {
this.beforeSetValidator = validator;
return this;
}
/**
* Sets the converter to be invoked when converting from the source type to
* the destination type.
*
* @param converter
* @return the receiver, to enable method call chaining
*/
public UpdateValueStrategy setConverter(IConverter converter) {
this.converter = converter;
return this;
}
/**
* Validates the value after it is converted.
* <p>
* Default implementation will use the
* {@link #setAfterConvertValidator(IValidator) validator} if one exists. If
* one does not exist no validation will occur.
* </p>
*
* @param value
* @return an ok status
*/
public IStatus validateAfterConvert(Object value) {
return afterConvertValidator == null ? Status.OK_STATUS
: afterConvertValidator.validate(value);
}
/**
* Validates the value after it is retrieved from the source.
* <p>
* Default implementation will use the
* {@link #setAfterGetValidator(IValidator) validator} if one exists. If one
* does not exist no validation will occur.
* </p>
*
* @param value
* @return an ok status
*/
public IStatus validateAfterGet(Object value) {
return afterGetValidator == null ? Status.OK_STATUS : afterGetValidator
.validate(value);
}
/**
* Validates the value before it is set on the destination.
* <p>
* Default implementation will use the
* {@link #setBeforeSetValidator(IValidator) validator} if one exists. If
* one does not exist no validation will occur.
* </p>
*
* @param value
* @return an ok status
*/
public IStatus validateBeforeSet(Object value) {
return beforeSetValidator == null ? Status.OK_STATUS
: beforeSetValidator.validate(value);
}
/**
* Sets the current value of the given observable to the given value.
* Clients may extend but must call the super implementation.
*
* @param observableValue
* @param value
* @return status
*/
protected IStatus doSet(IObservableValue observableValue, Object value) {
try {
observableValue.setValue(value);
} catch (Exception ex) {
return ValidationStatus.error(BindingMessages
.getString("ValueBinding_ErrorWhileSettingValue"), //$NON-NLS-1$
ex);
}
return Status.OK_STATUS;
}
private static class ValidatorRegistry {
private HashMap validators = new HashMap();
/**
* Adds the system-provided validators to the current validator
* registry. This is done automatically for the validator registry
* singleton.
*/
private ValidatorRegistry() {
// Standalone validators here...
associate(Integer.class, Integer.TYPE,
new ObjectToPrimitiveValidator(Integer.TYPE));
associate(Byte.class, Byte.TYPE, new ObjectToPrimitiveValidator(
Byte.TYPE));
associate(Short.class, Short.TYPE, new ObjectToPrimitiveValidator(
Short.TYPE));
associate(Long.class, Long.TYPE, new ObjectToPrimitiveValidator(
Long.TYPE));
associate(Float.class, Float.TYPE, new ObjectToPrimitiveValidator(
Float.TYPE));
associate(Double.class, Double.TYPE,
new ObjectToPrimitiveValidator(Double.TYPE));
associate(Boolean.class, Boolean.TYPE,
new ObjectToPrimitiveValidator(Boolean.TYPE));
associate(Object.class, Integer.TYPE,
new ObjectToPrimitiveValidator(Integer.TYPE));
associate(Object.class, Byte.TYPE, new ObjectToPrimitiveValidator(
Byte.TYPE));
associate(Object.class, Short.TYPE, new ObjectToPrimitiveValidator(
Short.TYPE));
associate(Object.class, Long.TYPE, new ObjectToPrimitiveValidator(
Long.TYPE));
associate(Object.class, Float.TYPE, new ObjectToPrimitiveValidator(
Float.TYPE));
associate(Object.class, Double.TYPE,
new ObjectToPrimitiveValidator(Double.TYPE));
associate(Object.class, Boolean.TYPE,
new ObjectToPrimitiveValidator(Boolean.TYPE));
}
/**
* Associate a particular validator that can validate the conversion
* (fromClass, toClass)
*
* @param fromClass
* The Class to convert from
* @param toClass
* The Class to convert to
* @param validator
* The IValidator
*/
private void associate(Object fromClass, Object toClass,
IValidator validator) {
validators.put(new Pair(fromClass, toClass), validator);
}
/**
* Return an IValidator for a specific fromClass and toClass.
*
* @param fromClass
* The Class to convert from
* @param toClass
* The Class to convert to
* @return An appropriate IValidator
*/
private IValidator get(Object fromClass, Object toClass) {
IValidator result = (IValidator) validators.get(new Pair(fromClass,
toClass));
if (result != null)
return result;
if (fromClass != null && toClass != null && fromClass == toClass) {
return new IValidator() {
public IStatus validate(Object value) {
return Status.OK_STATUS;
}
};
}
return new IValidator() {
public IStatus validate(Object value) {
return Status.OK_STATUS;
}
};
}
}
}