diff org.eclipse.core.databinding/src/org/eclipse/core/databinding/UpdateValueStrategy.d @ 78:0a55d2d5a946

Added file for databinding
author Frank Benoit <benoit@tionex.de>
date Tue, 14 Apr 2009 11:35:29 +0200
parents
children 383ce7bd736b
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding/src/org/eclipse/core/databinding/UpdateValueStrategy.d	Tue Apr 14 11:35:29 2009 +0200
@@ -0,0 +1,582 @@
+/*******************************************************************************
+ * Copyright (c) 2007, 2008 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
+ *******************************************************************************/
+
+module org.eclipse.core.databinding.UpdateValueStrategy;
+
+import java.lang.all;
+
+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 #validateAfterGetcast(Object)}</li>
+ * <li>Conversion - {@link #convertcast(Object)}</li>
+ * <li>Validate after conversion - {@link #validateAfterConvertcast(Object)}</li>
+ * <li>Validate before set - {@link #validateBeforeSetcast(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 : 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 #validateBeforeSetcast(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 bool provideDefaults;
+
+    /**
+     * <code>true</code> if we defaulted the converter
+     */
+    private bool 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 this() {
+        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 this(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 this(bool 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 #setConvertercast(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 is 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 is null || toType is null) {
+            return new class() 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 #validateAfterGetcast(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 !is null && destinationType !is null) {
+            if (converter is null) {
+                IConverter converter = createConverter(sourceType,
+                        destinationType);
+                defaultedConverter = (converter !is null);
+                setConverter(converter);
+            }
+
+            if (afterGetValidator is null) {
+                afterGetValidator = createValidator(sourceType, destinationType);
+            }
+        }
+        if (converter !is null) {
+            if (sourceType !is null) {
+                checkAssignable(converter.getFromType(), sourceType,
+                        "converter does not convert from type " + sourceType); //$NON-NLS-1$
+            }
+            if (destinationType !is 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.classinfo.equals(fromType)) {
+                result = cast(IValidator) validatorsByConverter.get(converter);
+
+                if (result is null) {
+                    // TODO sring based lookup
+                    if (Integer.classinfo.equals(toType)
+                            || Integer.TYPE.equals(toType)) {
+                        result = new StringToIntegerValidator(
+                                cast(NumberFormatConverter) converter);
+                    } else if (Long.classinfo.equals(toType)
+                            || Long.TYPE.equals(toType)) {
+                        result = new StringToLongValidator(
+                                cast(NumberFormatConverter) converter);
+                    } else if (Float.classinfo.equals(toType)
+                            || Float.TYPE.equals(toType)) {
+                        result = new StringToFloatValidator(
+                                cast(NumberFormatConverter) converter);
+                    } else if (Double.classinfo.equals(toType)
+                            || Double.TYPE.equals(toType)) {
+                        result = new StringToDoubleValidator(
+                                cast(NumberFormatConverter) converter);
+                    } else if (Byte.classinfo.equals(toType)
+                            || Byte.TYPE.equals(toType)) {
+                        result = new StringToByteValidator(
+                                cast(NumberFormatConverter) converter);
+                    } else if (Short.classinfo.equals(toType)
+                            || Short.TYPE.equals(toType)) {
+                        result = new StringToShortValidator(
+                                cast(NumberFormatConverter) converter);
+                    } else if (Character.classinfo.equals(toType)
+                            || Character.TYPE.equals(toType)
+                            && null !is cast(StringToCharacterConverter)converter ) {
+                        result = new StringToCharacterValidator(
+                                cast(StringToCharacterConverter) converter);
+                    } else if (Date.classinfo.equals(toType)
+                            && null !is cast(StringToDateConverter)converter ) {
+                        result = new StringToDateValidator(
+                                cast(StringToDateConverter) converter);
+                    }
+
+                    if (result !is null) {
+                        validatorsByConverter.put(converter, result);
+                    }
+                }
+            } else if ( null !is cast(NumberToNumberConverter)converter ) {
+                result = cast(IValidator) validatorsByConverter.get(converter);
+
+                if (result is null) {
+                    if ( null !is cast(NumberToByteConverter)converter ) {
+                        result = new NumberToByteValidator(
+                                cast(NumberToByteConverter) converter);
+                    } else if ( null !is cast(NumberToShortConverter)converter ) {
+                        result = new NumberToShortValidator(
+                                cast(NumberToShortConverter) converter);
+                    } else if ( null !is cast(NumberToIntegerConverter)converter ) {
+                        result = new NumberToIntegerValidator(
+                                cast(NumberToIntegerConverter) converter);
+                    } else if ( null !is cast(NumberToLongConverter)converter ) {
+                        result = new NumberToLongValidator(
+                                cast(NumberToLongConverter) converter);
+                    } else if ( null !is cast(NumberToFloatConverter)converter ) {
+                        result = new NumberToFloatValidator(
+                                cast(NumberToFloatConverter) converter);
+                    } else if ( null !is cast(NumberToDoubleConverter)converter ) {
+                        result = new NumberToDoubleValidator(
+                                cast(NumberToDoubleConverter) converter);
+                    } else if ( null !is cast(NumberToBigIntegerConverter)converter 
+                            || null !is cast(NumberToBigDecimalConverter)converter ) {
+                        result = new NumberToUnboundedNumberValidator(
+                                cast(NumberToNumberConverter) converter);
+                    }
+                }
+            }
+
+            if (result is 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 #setAfterConvertValidatorcast(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 is null ? Status.OK_STATUS
+                : afterConvertValidator.validate(value);
+    }
+
+    /**
+     * Validates the value after it is retrieved from the source.
+     * <p>
+     * Default implementation will use the
+     * {@link #setAfterGetValidatorcast(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 is null ? Status.OK_STATUS : afterGetValidator
+                .validate(value);
+    }
+
+    /**
+     * Validates the value before it is set on the destination.
+     * <p>
+     * Default implementation will use the
+     * {@link #setBeforeSetValidatorcast(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 is 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
+                    .getStringcast(BindingMessages.VALUEBINDING_ERROR_WHILE_SETTING_VALUE),
+                    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 this() {
+            // Standalone validators here...
+            associate(Integer.classinfo, Integer.TYPE,
+                    new ObjectToPrimitiveValidatorcast(Integer.TYPE));
+            associate(Byte.classinfo, Byte.TYPE, new ObjectToPrimitiveValidator(
+                    Byte.TYPE));
+            associate(Short.classinfo, Short.TYPE, new ObjectToPrimitiveValidator(
+                    Short.TYPE));
+            associate(Long.classinfo, Long.TYPE, new ObjectToPrimitiveValidator(
+                    Long.TYPE));
+            associate(Float.classinfo, Float.TYPE, new ObjectToPrimitiveValidator(
+                    Float.TYPE));
+            associate(Double.classinfo, Double.TYPE,
+                    new ObjectToPrimitiveValidatorcast(Double.TYPE));
+            associate(Boolean.classinfo, Boolean.TYPE,
+                    new ObjectToPrimitiveValidatorcast(Boolean.TYPE));
+
+            associate(Object.classinfo, Integer.TYPE,
+                    new ObjectToPrimitiveValidatorcast(Integer.TYPE));
+            associate(Object.classinfo, Byte.TYPE, new ObjectToPrimitiveValidator(
+                    Byte.TYPE));
+            associate(Object.classinfo, Short.TYPE, new ObjectToPrimitiveValidator(
+                    Short.TYPE));
+            associate(Object.classinfo, Long.TYPE, new ObjectToPrimitiveValidator(
+                    Long.TYPE));
+            associate(Object.classinfo, Float.TYPE, new ObjectToPrimitiveValidator(
+                    Float.TYPE));
+            associate(Object.classinfo, Double.TYPE,
+                    new ObjectToPrimitiveValidatorcast(Double.TYPE));
+            associate(Object.classinfo, Boolean.TYPE,
+                    new ObjectToPrimitiveValidatorcast(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 = cast(IValidator) validators.get(new Pair(fromClass,
+                    toClass));
+            if (result !is null)
+                return result;
+            if (fromClass !is null && toClass !is null && fromClass is toClass) {
+                return new class() IValidator {
+                    public IStatus validate(Object value) {
+                        return Status.OK_STATUS;
+                    }
+                };
+            }
+            return new class() IValidator {
+                public IStatus validate(Object value) {
+                    return Status.OK_STATUS;
+                }
+            };
+        }
+    }
+
+}