diff org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableMap.d @ 78:0a55d2d5a946

Added file for databinding
author Frank Benoit <benoit@tionex.de>
date Tue, 14 Apr 2009 11:35:29 +0200
parents
children 6be48cf9f95c
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/org.eclipse.core.databinding/src/org/eclipse/core/internal/databinding/observable/ValidatedObservableMap.d	Tue Apr 14 11:35:29 2009 +0200
@@ -0,0 +1,230 @@
+/*******************************************************************************
+ * Copyright (c) 2008 Matthew Hall 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:
+ *     Matthew Hall - initial API and implementation (bug 218269)
+ ******************************************************************************/
+
+module org.eclipse.core.internal.databinding.observable.ValidatedObservableMap;
+
+import java.lang.all;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.map.IMapChangeListener;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
+import org.eclipse.core.databinding.observable.map.MapChangeEvent;
+import org.eclipse.core.databinding.observable.map.MapDiff;
+import org.eclipse.core.databinding.observable.map.ObservableMap;
+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.Assert;
+import org.eclipse.core.runtime.IStatus;
+
+/**
+ * @since 3.3
+ * 
+ */
+public class ValidatedObservableMap : ObservableMap {
+    private IObservableMap target;
+    private IObservableValue validationStatus;
+
+    // Only true when out of sync with target due to validation status
+    private bool stale;
+
+    // True when validation status changes from invalid to valid.
+    private bool computeNextDiff = false;
+
+    private bool updatingTarget = false;
+
+    private IMapChangeListener targetChangeListener = new class() IMapChangeListener {
+        public void handleMapChange(MapChangeEvent event) {
+            if (updatingTarget)
+                return;
+            IStatus status = cast(IStatus) validationStatus.getValue();
+            if (isValid(status)) {
+                if (stale) {
+                    // this.stale means we are out of sync with target,
+                    // so reset wrapped list to exactly mirror target
+                    stale = false;
+                    updateWrappedMap(new HashMap(target));
+                } else {
+                    MapDiff diff = event.diff;
+                    if (computeNextDiff) {
+                        diff = Diffs.computeMapDiff(wrappedMap, target);
+                        computeNextDiff = false;
+                    }
+                    applyDiff(diff, wrappedMap);
+                    fireMapChange(diff);
+                }
+            } else {
+                makeStale();
+            }
+        }
+    };
+
+    private IStaleListener targetStaleListener = new class() IStaleListener {
+        public void handleStale(StaleEvent staleEvent) {
+            fireStale();
+        }
+    };
+
+    private IValueChangeListener validationStatusChangeListener = new class() IValueChangeListener {
+        public void handleValueChange(ValueChangeEvent event) {
+            IStatus oldStatus = cast(IStatus) event.diff.getOldValue();
+            IStatus newStatus = cast(IStatus) event.diff.getNewValue();
+            if (stale && !isValid(oldStatus) && isValid(newStatus)) {
+                // this.stale means we are out of sync with target,
+                // reset wrapped map to exactly mirror target
+                stale = false;
+                updateWrappedMap(new HashMap(target));
+
+                // If the validation status becomes valid because of a change in
+                // target observable
+                computeNextDiff = true;
+            }
+        }
+    };
+
+    /**
+     * @param target
+     * @param validationStatus
+     */
+    public this(IObservableMap target,
+            IObservableValue validationStatus) {
+        super(target.getRealm(), new HashMap(target));
+        Assert.isNotNull(validationStatus,
+                "Validation status observable cannot be null"); //$NON-NLS-1$
+        Assert
+                .isTrue(target.getRealm().equals(validationStatus.getRealm()),
+                        "Target and validation status observables must be on the same realm"); //$NON-NLS-1$
+        this.target = target;
+        this.validationStatus = validationStatus;
+        target.addMapChangeListener(targetChangeListener);
+        target.addStaleListener(targetStaleListener);
+        validationStatus.addValueChangeListener(validationStatusChangeListener);
+    }
+
+    private void updateWrappedMap(Map newMap) {
+        Map oldMap = wrappedMap;
+        MapDiff diff = Diffs.computeMapDiff(oldMap, newMap);
+        wrappedMap = newMap;
+        fireMapChange(diff);
+    }
+
+    private static bool isValid(IStatus status) {
+        return status.isOK() || status.matches(IStatus.INFO | IStatus.WARNING);
+    }
+
+    private void applyDiff(MapDiff diff, Map map) {
+        for (Iterator iterator = diff.getRemovedKeys().iterator(); iterator
+                .hasNext();)
+            map.remove(iterator.next());
+        for (Iterator iterator = diff.getChangedKeys().iterator(); iterator
+                .hasNext();) {
+            Object key = iterator.next();
+            map.put(key, diff.getNewValue(key));
+        }
+        for (Iterator iterator = diff.getAddedKeys().iterator(); iterator
+                .hasNext();) {
+            Object key = iterator.next();
+            map.put(key, diff.getNewValue(key));
+        }
+    }
+
+    private void makeStale() {
+        if (!stale) {
+            stale = true;
+            fireStale();
+        }
+    }
+
+    private void updateTargetMap(MapDiff diff) {
+        updatingTarget = true;
+        try {
+            if (stale) {
+                stale = false;
+                applyDiff(Diffs.computeMapDiff(target, wrappedMap), target);
+            } else {
+                applyDiff(diff, target);
+            }
+        } finally {
+            updatingTarget = false;
+        }
+    }
+
+    public bool isStale() {
+        getterCalled();
+        return stale || target.isStale();
+    }
+
+    public void clear() {
+        checkRealm();
+        if (isEmpty())
+            return;
+        MapDiff diff = Diffs.computeMapDiff(wrappedMap, Collections.EMPTY_MAP);
+        wrappedMap = new HashMap();
+        updateTargetMap(diff);
+        fireMapChange(diff);
+    }
+
+    public Object put(Object key, Object value) {
+        checkRealm();
+        MapDiff diff;
+        Object oldValue;
+        if (wrappedMap.containsKey(key)) {
+            oldValue = wrappedMap.put(key, value);
+            if (wrappedMap.containsKey(key)) { // Changed
+                diff = Diffs.createMapDiffSingleChange(key, oldValue, value);
+            } else { // Removed
+                diff = Diffs.createMapDiffSingleRemove(key, oldValue);
+            }
+        } else { // Added
+            oldValue = wrappedMap.put(key, value);
+            diff = Diffs.createMapDiffSingleAdd(key, value);
+        }
+        updateTargetMap(diff);
+        fireMapChange(diff);
+        return oldValue;
+    }
+
+    public void putAll(Map m) {
+        checkRealm();
+        Map map = new HashMap(wrappedMap);
+        map.putAll(m);
+        MapDiff diff = Diffs.computeMapDiff(wrappedMap, map);
+        wrappedMap = map;
+        updateTargetMap(diff);
+        fireMapChange(diff);
+    }
+
+    public Object remove(Object key) {
+        checkRealm();
+        if (!wrappedMap.containsKey(key))
+            return null;
+        Object oldValue = wrappedMap.remove(key);
+        MapDiff diff = Diffs.createMapDiffSingleRemove(key, oldValue);
+        updateTargetMap(diff);
+        fireMapChange(diff);
+        return oldValue;
+    }
+
+    public synchronized void dispose() {
+        target.removeMapChangeListener(targetChangeListener);
+        target.removeStaleListener(targetStaleListener);
+        validationStatus
+                .removeValueChangeListener(validationStatusChangeListener);
+        super.dispose();
+    }
+}