| /******************************************************************************* |
| * Copyright (c) 2007, 2009 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) |
| * Tom Schindl <tom.schindl@bestsolution.at> - bugfix for 217940 |
| * Matthew Hall <matthall@woodcraftmill.com> - bug 270461 |
| *******************************************************************************/ |
| |
| package org.eclipse.core.databinding; |
| |
| import java.util.Date; |
| import java.util.HashMap; |
| import java.util.Map; |
| |
| 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> |
| * |
| * @param <S> |
| * the type of the value on the source side (i.e. the model side if |
| * this is a model-to-target update and the target side if this is a |
| * target-to-model update) |
| * @param <D> |
| * the type of the value on the destination side (i.e. the target |
| * side if this is a model-to-target update and the model side if |
| * this is a target-to-model update) |
| * @see DataBindingContext#bindValue(IObservableValue, IObservableValue, |
| * UpdateValueStrategy, UpdateValueStrategy) |
| * @see Binding#getValidationStatus() |
| * @see IValidator |
| * @see IConverter |
| * @since 1.0 |
| */ |
| public class UpdateValueStrategy<S, D> 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<S> afterGetValidator; |
| protected IValidator<D> afterConvertValidator; |
| protected IValidator<D> beforeSetValidator; |
| protected IConverter<S, D> converter; |
| |
| private int updatePolicy; |
| |
| private static ValidatorRegistry validatorRegistry = new ValidatorRegistry(); |
| private static HashMap<IConverter<?, ?>, IValidator<?>> validatorsByConverter = new HashMap<IConverter<?, ?>, IValidator<?>>(); |
| |
| 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 D convert(S value) { |
| // FIXME If converter is null then S must be assignable to T |
| // or an exception will occur here. |
| return converter == null ? (D) 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<S> createValidator(Object fromType, Object toType) { |
| if (fromType == null || toType == null) { |
| return new IValidator<S>() { |
| |
| public IStatus validate(S 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 <S2, T2> void fillDefaults(IObservableValue<S2> source, |
| IObservableValue<T2> destination) { |
| Object sourceType = source.getValueType(); |
| Object destinationType = destination.getValueType(); |
| if (provideDefaults && sourceType != null && destinationType != null) { |
| if (converter == null) { |
| IConverter<S, D> converter = (IConverter<S, D>) 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(destinationType, converter.getToType(), |
| "converter does not convert to type " + destinationType); //$NON-NLS-1$ |
| } |
| } |
| } |
| |
| private IValidator<S> 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 = 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 = 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 (IValidator<S>) 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<S, D> setAfterConvertValidator( |
| IValidator<D> 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<S, D> setAfterGetValidator( |
| IValidator<S> 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<S, D> setBeforeSetValidator( |
| IValidator<D> 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<S, D> setConverter(IConverter<S, D> 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(D 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(S 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(D 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 <V> IStatus doSet(IObservableValue<V> observableValue, V value) { |
| try { |
| observableValue.setValue(value); |
| } catch (Exception ex) { |
| return ValidationStatus |
| .error(BindingMessages |
| .getString(BindingMessages.VALUEBINDING_ERROR_WHILE_SETTING_VALUE), |
| ex); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| private static class ValidatorRegistry { |
| |
| private Map<Pair, IValidator<?>> validators = new HashMap<Pair, IValidator<?>>(); |
| |
| /** |
| * 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 = validators.get(new Pair(fromClass, toClass)); |
| if (result != null) |
| return result; |
| if (fromClass != null && toClass != null && fromClass == toClass) { |
| return new IValidator<Object>() { |
| public IStatus validate(Object value) { |
| return Status.OK_STATUS; |
| } |
| }; |
| } |
| return new IValidator<Object>() { |
| public IStatus validate(Object value) { |
| return Status.OK_STATUS; |
| } |
| }; |
| } |
| } |
| |
| } |