### Eclipse Workspace Patch 1.0 #P org.eclipse.core.databinding.observable Index: META-INF/MANIFEST.MF =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.observable/META-INF/MANIFEST.MF,v retrieving revision 1.5 diff -u -r1.5 MANIFEST.MF --- META-INF/MANIFEST.MF 25 Aug 2009 04:57:26 -0000 1.5 +++ META-INF/MANIFEST.MF 11 Dec 2010 01:48:11 -0000 @@ -2,7 +2,7 @@ Bundle-ManifestVersion: 2 Bundle-Name: %pluginName Bundle-SymbolicName: org.eclipse.core.databinding.observable -Bundle-Version: 1.3.0.qualifier +Bundle-Version: 1.4.0.qualifier Bundle-ClassPath: . Bundle-Vendor: %providerName Bundle-Localization: plugin Index: src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java =================================================================== RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.observable/src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java,v retrieving revision 1.10 diff -u -r1.10 MasterDetailObservables.java --- src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java 17 Oct 2008 16:44:25 -0000 1.10 +++ src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java 11 Dec 2010 01:48:11 -0000 @@ -9,6 +9,7 @@ * IBM Corporation - initial API and implementation * Brad Reynolds - bug 147515 * Matthew Hall - bug 221704, 226289 + * Ovidio Mallo - bugs 305367 *******************************************************************************/ package org.eclipse.core.databinding.observable.masterdetail; @@ -21,6 +22,9 @@ import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableMap; import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableSet; import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableValue; +import org.eclipse.core.internal.databinding.observable.masterdetail.ListDetailValueObservableList; +import org.eclipse.core.internal.databinding.observable.masterdetail.MapDetailValueObservableMap; +import org.eclipse.core.internal.databinding.observable.masterdetail.SetDetailValueObservableMap; /** * Allows for the observation of an attribute, the detail, of an observable @@ -29,7 +33,7 @@ * @since 1.0 */ public class MasterDetailObservables { - + /** * Creates a detail observable value from a master observable value and a * factory. This can be used to create observable values that represent a @@ -145,4 +149,119 @@ return new DetailObservableMap(detailFactory, master, detailKeyType, detailValueType); } + + /** + * Returns a detail observable list where each element is the detail value + * of the element in the master observable list. The provided factory is + * used to create the detail observable values for every master element + * which then define the elements of the detail list. The detail list + * resides in the same realm as the given master list. + * + *
+ * Note that since the values of the returned list are detail values of the + * elements of the master list, the only modifications supported are through + * the {@link IObservableList#set(int, Object)} method. Modifications made + * through the returned list are made through the detail observables created + * by the specified observable factory. + *
+ * + * @param masterList + * The master observable list. + * @param detailFactory + * The factory for creating {@link IObservableValue} instances + * for the elements of the master list which then define the + * elements of the new detail list. + * @param detailType + * The value type of the detail values, typically of type + *java.lang.Class
. May be null
.
+ * @return A detail observable list with elements which correspond to the
+ * detail values of the elements of the master list.
+ *
+ * @since 1.4
+ */
+ public static IObservableList detailValues(IObservableList masterList,
+ IObservableFactory detailFactory, Object detailType) {
+ return new ListDetailValueObservableList(masterList, detailFactory,
+ detailType);
+ }
+
+ /**
+ * Returns a detail observable map where the map's key set is the same as
+ * the given observable set, and where each value is the detail value of the
+ * element in the master observable set. The provided factory is used to
+ * create the detail observable values for every master key which then
+ * define the values of the detail map. The detail map resides in the same
+ * realm as the given master set.
+ *
+ * + * Note that since the values of the returned map are detail values of the + * elements of the master set, the only modifications supported are through + * the {@link IObservableMap#put(Object, Object)} and + * {@link IObservableMap#putAll(java.util.Map)} methods. Therefore, the + * returned map does not add entries for elements not already contained in + * the master set. Modifications made through the returned detail map are + * made through the detail observables created by the specified observable + * factory. + *
+ * + * @param masterSet + * The master observable set. + * @param detailFactory + * The factory for creating {@link IObservableValue} instances + * for the elements of the master set which then define the + * values of the new detail map. + * @param detailType + * The value type of the detail values, typically of type + *java.lang.Class
. May be null
.
+ * @return A detail observable map with the given master set as key set and
+ * with values which correspond to the detail values of the elements
+ * of the master set.
+ *
+ * @since 1.4
+ */
+ public static IObservableMap detailValues(IObservableSet masterSet,
+ IObservableFactory detailFactory, Object detailType) {
+ return new SetDetailValueObservableMap(masterSet, detailFactory,
+ detailType);
+ }
+
+ /**
+ * Returns a detail observable map where the map's key set is the same as
+ * the one of the given master observable map, and where each value is the
+ * detail value of the corresponding value in the master observable map. The
+ * provided factory is used to create the detail observable values for every
+ * master value which then define the values of the detail map. The detail
+ * map resides in the same realm as the given master map.
+ *
+ * + * Note that since the values of the returned map are detail values of the + * values of the master map, the only modifications supported are through + * the {@link IObservableMap#put(Object, Object)} and + * {@link IObservableMap#putAll(java.util.Map)} methods. Therefore, the + * returned map does not add entries for keys not already contained in the + * master map's key set. Modifications made through the returned detail map + * are made through the detail observables created by the specified + * observable factory. + *
+ * + * @param masterMap + * The master observable map. + * @param detailFactory + * The factory for creating {@link IObservableValue} instances + * for the values of the master map which then define the values + * of the new detail map. + * @param detailType + * The value type of the detail values, typically of type + *java.lang.Class
. May be null
.
+ * @return A detail observable map with the same key set as the given master
+ * observable map and with values which correspond to the detail
+ * values of the values of the master map.
+ *
+ * @since 1.4
+ */
+ public static IObservableMap detailValues(IObservableMap masterMap,
+ IObservableFactory detailFactory, Object detailType) {
+ return new MapDetailValueObservableMap(masterMap, detailFactory,
+ detailType);
+ }
}
Index: src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java
===================================================================
RCS file: src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java
diff -N src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,351 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Ovidio Mallo 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:
+ * Ovidio Mallo - initial API and implementation (bug 305367)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.masterdetail;
+
+import java.util.ArrayList;
+import java.util.BitSet;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.RandomAccess;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.DisposeEvent;
+import org.eclipse.core.databinding.observable.IDisposeListener;
+import org.eclipse.core.databinding.observable.IObserving;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.list.AbstractObservableList;
+import org.eclipse.core.databinding.observable.list.IListChangeListener;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.list.ListChangeEvent;
+import org.eclipse.core.databinding.observable.list.ListDiff;
+import org.eclipse.core.databinding.observable.list.ListDiffEntry;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+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.internal.databinding.identity.IdentityMap;
+import org.eclipse.core.internal.databinding.identity.IdentitySet;
+
+/**
+ * @since 1.4
+ */
+public class ListDetailValueObservableList extends AbstractObservableList
+ implements IObserving, RandomAccess {
+
+ private IObservableList masterList;
+
+ private IObservableFactory detailFactory;
+
+ private Object detailType;
+
+ // The list of detail observables.
+ private ArrayList detailList;
+
+ // Maps every master to a DetailEntry containing the detail observable. This
+ // map is used to avoid that multiple detail observables are created for the
+ // same master.
+ private IdentityMap masterDetailMap = new IdentityMap();
+
+ private IdentitySet staleDetailObservables = new IdentitySet();
+
+ private IListChangeListener masterListListener = new IListChangeListener() {
+ public void handleListChange(ListChangeEvent event) {
+ handleMasterListChange(event.diff);
+ }
+ };
+
+ private IValueChangeListener detailValueListener = new IValueChangeListener() {
+ public void handleValueChange(ValueChangeEvent event) {
+ if (!event.getObservable().isStale()) {
+ staleDetailObservables.remove(event.getObservable());
+ }
+ handleDetailValueChange(event);
+ }
+ };
+
+ private IStaleListener masterStaleListener = new IStaleListener() {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ };
+
+ private IStaleListener detailStaleListener = new IStaleListener() {
+ public void handleStale(StaleEvent staleEvent) {
+ boolean wasStale = isStale();
+ staleDetailObservables.add((staleEvent.getObservable()));
+ if (!wasStale) {
+ fireStale();
+ }
+ }
+ };
+
+ /**
+ *
+ * @param masterList
+ * @param detailFactory
+ * @param detailType
+ */
+ public ListDetailValueObservableList(IObservableList masterList,
+ IObservableFactory detailFactory, Object detailType) {
+ super(masterList.getRealm());
+ this.masterList = masterList;
+ this.detailFactory = detailFactory;
+ this.detailType = detailType;
+ this.detailList = new ArrayList();
+
+ // Add change/stale/dispose listeners on the master list.
+ masterList.addListChangeListener(masterListListener);
+ masterList.addStaleListener(masterStaleListener);
+ masterList.addDisposeListener(new IDisposeListener() {
+ public void handleDispose(DisposeEvent event) {
+ ListDetailValueObservableList.this.dispose();
+ }
+ });
+
+ ListDiff initMasterDiff = Diffs.computeListDiff(Collections.EMPTY_LIST,
+ masterList);
+ handleMasterListChange(initMasterDiff);
+ }
+
+ protected synchronized void firstListenerAdded() {
+ for (int i = 0; i < detailList.size(); i++) {
+ IObservableValue detail = (IObservableValue) detailList.get(i);
+ detail.addValueChangeListener(detailValueListener);
+ detail.addStaleListener(detailStaleListener);
+ if (detail.isStale()) {
+ staleDetailObservables.add(detail);
+ }
+ }
+ }
+
+ protected synchronized void lastListenerRemoved() {
+ if (isDisposed()) {
+ return;
+ }
+
+ for (int i = 0; i < detailList.size(); i++) {
+ IObservableValue detail = (IObservableValue) detailList.get(i);
+ detail.removeValueChangeListener(detailValueListener);
+ detail.removeStaleListener(detailStaleListener);
+ }
+ staleDetailObservables.clear();
+ }
+
+ private void handleMasterListChange(ListDiff masterListDiff) {
+ boolean wasStale = isStale();
+
+ boolean hasListeners = hasListeners();
+ ListDiffEntry[] masterEntries = masterListDiff.getDifferences();
+ ListDiffEntry[] detailEntries = new ListDiffEntry[masterEntries.length];
+ for (int i = 0; i < masterEntries.length; i++) {
+ ListDiffEntry masterEntry = masterEntries[i];
+ int index = masterEntry.getPosition();
+
+ Object masterElement = masterEntry.getElement();
+ Object detailValue;
+ if (masterEntry.isAddition()) {
+ detailValue = addDetailObservable(masterElement, index);
+ } else {
+ detailValue = removeDetailObservable(masterElement, index);
+ }
+
+ if (hasListeners) {
+ // Create the corresponding diff for the detail list.
+ detailEntries[i] = Diffs.createListDiffEntry(index,
+ masterEntry.isAddition(), detailValue);
+ }
+ }
+
+ if (hasListeners) {
+ if (!wasStale && isStale()) {
+ fireStale();
+ }
+
+ // Fire a list change event with the adapted diff.
+ fireListChange(Diffs.createListDiff(detailEntries));
+ }
+ }
+
+ private Object addDetailObservable(Object masterElement, int index) {
+ DetailEntry detailEntry = (DetailEntry) masterDetailMap
+ .get(masterElement);
+ if (detailEntry != null) {
+ // If we already have a detail observable for the given
+ // masterElement, we increment the reference count.
+ detailEntry.masterReferenceCount++;
+ detailList.add(index, detailEntry.detailObservable);
+ return detailEntry.detailObservable.getValue();
+ }
+
+ IObservableValue detail = createDetailObservable(masterElement);
+ masterDetailMap.put(masterElement, new DetailEntry(detail));
+
+ detailList.add(index, detail);
+
+ if (hasListeners()) {
+ detail.addValueChangeListener(detailValueListener);
+ detail.addStaleListener(detailStaleListener);
+ if (detail.isStale()) {
+ staleDetailObservables.add(detail);
+ }
+ }
+
+ return detail.getValue();
+ }
+
+ private Object removeDetailObservable(Object masterElement, int index) {
+ IObservableValue detail = (IObservableValue) detailList.remove(index);
+ Object detailValue = detail.getValue();
+
+ DetailEntry detailEntry = (DetailEntry) masterDetailMap
+ .get(masterElement);
+
+ // We may only dispose the detail observable ASA there are no more
+ // masters referencing it.
+ detailEntry.masterReferenceCount--;
+ if (detailEntry.masterReferenceCount == 0) {
+ masterDetailMap.remove(masterElement);
+ staleDetailObservables.remove(detail);
+ detail.dispose();
+ }
+
+ return detailValue;
+ }
+
+ private void handleDetailValueChange(ValueChangeEvent event) {
+ IObservableValue detail = event.getObservableValue();
+
+ // When we get a change event on a detail observable, we must find its
+ // position while there may also be duplicate entries.
+ BitSet detailIndexes = new BitSet();
+ for (int i = 0; i < detailList.size(); i++) {
+ if (detailList.get(i) == detail) {
+ detailIndexes.set(i);
+ }
+ }
+
+ // Create the diff for every found position.
+ Object oldValue = event.diff.getOldValue();
+ Object newValue = event.diff.getNewValue();
+ ListDiffEntry[] diffEntries = new ListDiffEntry[2 * detailIndexes
+ .cardinality()];
+ int diffIndex = 0;
+ for (int b = detailIndexes.nextSetBit(0); b != -1; b = detailIndexes
+ .nextSetBit(b + 1)) {
+ diffEntries[diffIndex++] = Diffs.createListDiffEntry(b, false,
+ oldValue);
+ diffEntries[diffIndex++] = Diffs.createListDiffEntry(b, true,
+ newValue);
+ }
+ fireListChange(Diffs.createListDiff(diffEntries));
+ }
+
+ private IObservableValue createDetailObservable(Object masterElement) {
+ ObservableTracker.setIgnore(true);
+ try {
+ return (IObservableValue) detailFactory
+ .createObservable(masterElement);
+ } finally {
+ ObservableTracker.setIgnore(false);
+ }
+ }
+
+ protected int doGetSize() {
+ return detailList.size();
+ }
+
+ public Object get(int index) {
+ ObservableTracker.getterCalled(this);
+ return ((IObservableValue) detailList.get(index)).getValue();
+ }
+
+ public Object set(int index, Object element) {
+ IObservableValue detail = (IObservableValue) detailList.get(index);
+ Object oldElement = detail.getValue();
+ detail.setValue(element);
+ return oldElement;
+ }
+
+ public Object move(int oldIndex, int newIndex) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean remove(Object o) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean removeAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public boolean retainAll(Collection c) {
+ throw new UnsupportedOperationException();
+ }
+
+ public void clear() {
+ throw new UnsupportedOperationException();
+ }
+
+ public Object getElementType() {
+ return detailType;
+ }
+
+ public boolean isStale() {
+ return super.isStale()
+ || (masterList != null && masterList.isStale())
+ || (staleDetailObservables != null && !staleDetailObservables
+ .isEmpty());
+ }
+
+ public Object getObserved() {
+ return masterList;
+ }
+
+ public synchronized void dispose() {
+ if (masterList != null) {
+ masterList.removeListChangeListener(masterListListener);
+ masterList.removeStaleListener(masterStaleListener);
+ }
+
+ if (detailList != null) {
+ for (Iterator iter = detailList.iterator(); iter.hasNext();) {
+ IObservableValue detailValue = (IObservableValue) iter.next();
+ detailValue.dispose();
+ }
+ detailList.clear();
+ }
+
+ masterList = null;
+ detailFactory = null;
+ detailType = null;
+ masterListListener = null;
+ detailValueListener = null;
+ masterDetailMap = null;
+ staleDetailObservables = null;
+
+ super.dispose();
+ }
+
+ private static final class DetailEntry {
+
+ private final IObservableValue detailObservable;
+
+ private int masterReferenceCount = 1;
+
+ public DetailEntry(IObservableValue detailObservable) {
+ this.detailObservable = detailObservable;
+ }
+ }
+}
Index: src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java
===================================================================
RCS file: src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java
diff -N src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,405 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Ovidio Mallo 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:
+ * Ovidio Mallo - initial API and implementation (bug 305367)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.masterdetail;
+
+import java.util.AbstractSet;
+import java.util.Collections;
+import java.util.IdentityHashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.Set;
+
+import org.eclipse.core.databinding.observable.Diffs;
+import org.eclipse.core.databinding.observable.DisposeEvent;
+import org.eclipse.core.databinding.observable.IDisposeListener;
+import org.eclipse.core.databinding.observable.IObserving;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.map.AbstractObservableMap;
+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.masterdetail.IObservableFactory;
+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.internal.databinding.identity.IdentityMap;
+import org.eclipse.core.internal.databinding.identity.IdentitySet;
+import org.eclipse.core.internal.databinding.observable.Util;
+
+/**
+ * @since 1.4
+ */
+public class MapDetailValueObservableMap extends AbstractObservableMap
+ implements IObserving {
+
+ private IObservableMap masterMap;
+
+ private IObservableFactory observableValueFactory;
+
+ private Object detailValueType;
+
+ private Set entrySet;
+
+ private IdentityHashMap keyDetailMap = new IdentityHashMap();
+
+ private IdentitySet staleDetailObservables = new IdentitySet();
+
+ private IMapChangeListener masterMapListener = new IMapChangeListener() {
+ public void handleMapChange(MapChangeEvent event) {
+ handleMasterMapChange(event.diff);
+ }
+ };
+
+ private IStaleListener masterStaleListener = new IStaleListener() {
+ public void handleStale(StaleEvent staleEvent) {
+ fireStale();
+ }
+ };
+
+ private IStaleListener detailStaleListener = new IStaleListener() {
+ public void handleStale(StaleEvent staleEvent) {
+ addStaleDetailObservable((IObservableValue) staleEvent
+ .getObservable());
+ }
+ };
+
+ /**
+ * @param masterMap
+ * @param observableValueFactory
+ * @param detailValueType
+ */
+ public MapDetailValueObservableMap(IObservableMap masterMap,
+ IObservableFactory observableValueFactory, Object detailValueType) {
+ super(masterMap.getRealm());
+ this.masterMap = masterMap;
+ this.observableValueFactory = observableValueFactory;
+ this.detailValueType = detailValueType;
+
+ // Add change/stale/dispose listeners on the master map.
+ masterMap.addMapChangeListener(masterMapListener);
+ masterMap.addStaleListener(masterStaleListener);
+ masterMap.addDisposeListener(new IDisposeListener() {
+ public void handleDispose(DisposeEvent event) {
+ MapDetailValueObservableMap.this.dispose();
+ }
+ });
+
+ // Initialize the map with the current state of the master map.
+ MapDiff initMasterDiff = Diffs.computeMapDiff(Collections.EMPTY_MAP,
+ masterMap);
+ handleMasterMapChange(initMasterDiff);
+ }
+
+ private void handleMasterMapChange(MapDiff diff) {
+ // Collect the detail values for the master values in the input diff.
+ IdentityMap oldValues = new IdentityMap();
+ IdentityMap newValues = new IdentityMap();
+
+ // Handle added master values.
+ Set addedKeys = diff.getAddedKeys();
+ for (Iterator iter = addedKeys.iterator(); iter.hasNext();) {
+ Object addedKey = iter.next();
+
+ // For added master values, we set up a new detail observable.
+ addDetailObservable(addedKey);
+
+ // Get the value of the created detail observable for the new diff.
+ IObservableValue detailValue = getDetailObservableValue(addedKey);
+ newValues.put(addedKey, detailValue.getValue());
+ }
+
+ // Handle removed master values.
+ Set removedKeys = diff.getRemovedKeys();
+ for (Iterator iter = removedKeys.iterator(); iter.hasNext();) {
+ Object removedKey = iter.next();
+
+ // First of all, get the current detail value and add it to the set
+ // of old values of the new diff.
+ IObservableValue detailValue = getDetailObservableValue(removedKey);
+ oldValues.put(removedKey, detailValue.getValue());
+
+ // For removed master values, we dispose the detail observable.
+ removeDetailObservable(removedKey);
+ }
+
+ // Handle changed master values.
+ Set changedKeys = diff.getChangedKeys();
+ for (Iterator iter = changedKeys.iterator(); iter.hasNext();) {
+ Object changedKey = iter.next();
+
+ // Get the detail value prior to the change and add it to the set of
+ // old values of the new diff.
+ IObservableValue oldDetailValue = getDetailObservableValue(changedKey);
+ oldValues.put(changedKey, oldDetailValue.getValue());
+
+ // Remove the old detail value for the old master value and add it
+ // again for the new master value.
+ removeDetailObservable(changedKey);
+ addDetailObservable(changedKey);
+
+ // Get the new detail value and add it to the set of new values.
+ IObservableValue newDetailValue = getDetailObservableValue(changedKey);
+ newValues.put(changedKey, newDetailValue.getValue());
+ }
+
+ // The different key sets are the same, only the values change.
+ fireMapChange(Diffs.createMapDiff(addedKeys, removedKeys, changedKeys,
+ oldValues, newValues));
+ }
+
+ private void addDetailObservable(final Object addedKey) {
+ Object masterElement = masterMap.get(addedKey);
+
+ IObservableValue detailValue = (IObservableValue) keyDetailMap
+ .get(addedKey);
+
+ if (detailValue == null) {
+ detailValue = createDetailObservable(masterElement);
+
+ keyDetailMap.put(addedKey, detailValue);
+
+ detailValue.addValueChangeListener(new IValueChangeListener() {
+ public void handleValueChange(ValueChangeEvent event) {
+ if (!event.getObservableValue().isStale()) {
+ staleDetailObservables.remove(event.getSource());
+ }
+
+ fireMapChange(Diffs.createMapDiffSingleChange(addedKey,
+ event.diff.getOldValue(), event.diff.getNewValue()));
+ }
+ });
+
+ if (detailValue.isStale()) {
+ addStaleDetailObservable(detailValue);
+ }
+ }
+
+ detailValue.addStaleListener(detailStaleListener);
+ }
+
+ private IObservableValue createDetailObservable(Object masterElement) {
+ ObservableTracker.setIgnore(true);
+ try {
+ return (IObservableValue) observableValueFactory
+ .createObservable(masterElement);
+ } finally {
+ ObservableTracker.setIgnore(false);
+ }
+ }
+
+ private void removeDetailObservable(Object removedKey) {
+ if (isDisposed()) {
+ return;
+ }
+
+ IObservableValue detailValue = (IObservableValue) keyDetailMap
+ .remove(removedKey);
+ staleDetailObservables.remove(detailValue);
+ detailValue.dispose();
+ }
+
+ private IObservableValue getDetailObservableValue(Object masterKey) {
+ return (IObservableValue) keyDetailMap.get(masterKey);
+ }
+
+ private void addStaleDetailObservable(IObservableValue detailObservable) {
+ boolean wasStale = isStale();
+ staleDetailObservables.add(detailObservable);
+ if (!wasStale) {
+ fireStale();
+ }
+ }
+
+ public Set keySet() {
+ getterCalled();
+
+ return masterMap.keySet();
+ }
+
+ public Object get(Object key) {
+ getterCalled();
+
+ if (!containsKey(key)) {
+ return null;
+ }
+
+ IObservableValue detailValue = getDetailObservableValue(key);
+ return detailValue.getValue();
+ }
+
+ public Object put(Object key, Object value) {
+ if (!containsKey(key)) {
+ return null;
+ }
+
+ IObservableValue detailValue = getDetailObservableValue(key);
+ Object oldValue = detailValue.getValue();
+ detailValue.setValue(value);
+ return oldValue;
+ }
+
+ public boolean containsKey(Object key) {
+ getterCalled();
+
+ return masterMap.containsKey(key);
+ }
+
+ public Object remove(Object key) {
+ checkRealm();
+
+ if (!containsKey(key)) {
+ return null;
+ }
+
+ IObservableValue detailValue = getDetailObservableValue(key);
+ Object oldValue = detailValue.getValue();
+
+ masterMap.remove(key);
+
+ return oldValue;
+ }
+
+ public int size() {
+ getterCalled();
+
+ return masterMap.size();
+ }
+
+ public boolean isStale() {
+ return super.isStale()
+ || (masterMap != null && masterMap.isStale())
+ || (staleDetailObservables != null && !staleDetailObservables
+ .isEmpty());
+ }
+
+ public Object getKeyType() {
+ return masterMap.getKeyType();
+ }
+
+ public Object getValueType() {
+ return detailValueType;
+ }
+
+ public Object getObserved() {
+ return masterMap;
+ }
+
+ public synchronized void dispose() {
+ if (masterMap != null) {
+ masterMap.removeMapChangeListener(masterMapListener);
+ masterMap.removeStaleListener(masterStaleListener);
+ }
+
+ if (keyDetailMap != null) {
+ for (Iterator iter = keyDetailMap.values().iterator(); iter
+ .hasNext();) {
+ IObservableValue detailValue = (IObservableValue) iter.next();
+ detailValue.dispose();
+ }
+ keyDetailMap.clear();
+ }
+
+ masterMap = null;
+ observableValueFactory = null;
+ detailValueType = null;
+ keyDetailMap = null;
+ masterStaleListener = null;
+ detailStaleListener = null;
+ staleDetailObservables = null;
+
+ super.dispose();
+ }
+
+ public Set entrySet() {
+ getterCalled();
+
+ if (entrySet == null) {
+ entrySet = new EntrySet();
+ }
+ return entrySet;
+ }
+
+ private void getterCalled() {
+ ObservableTracker.getterCalled(this);
+ }
+
+ private class EntrySet extends AbstractSet {
+
+ public Iterator iterator() {
+ final Iterator keyIterator = keySet().iterator();
+ return new Iterator() {
+
+ public boolean hasNext() {
+ return keyIterator.hasNext();
+ }
+
+ public Object next() {
+ Object key = keyIterator.next();
+ return new MapEntry(key);
+ }
+
+ public void remove() {
+ keyIterator.remove();
+ }
+ };
+ }
+
+ public int size() {
+ return MapDetailValueObservableMap.this.size();
+ }
+ }
+
+ private final class MapEntry implements Map.Entry {
+
+ private final Object key;
+
+ private MapEntry(Object key) {
+ this.key = key;
+ }
+
+ public Object getKey() {
+ MapDetailValueObservableMap.this.getterCalled();
+ return key;
+ }
+
+ public Object getValue() {
+ return MapDetailValueObservableMap.this.get(getKey());
+ }
+
+ public Object setValue(Object value) {
+ return MapDetailValueObservableMap.this.put(getKey(), value);
+ }
+
+ public boolean equals(Object o) {
+ MapDetailValueObservableMap.this.getterCalled();
+ if (o == this)
+ return true;
+ if (o == null)
+ return false;
+ if (!(o instanceof Map.Entry))
+ return false;
+ Map.Entry that = (Map.Entry) o;
+ return Util.equals(this.getKey(), that.getKey())
+ && Util.equals(this.getValue(), that.getValue());
+ }
+
+ public int hashCode() {
+ MapDetailValueObservableMap.this.getterCalled();
+ Object value = getValue();
+ return (getKey() == null ? 0 : getKey().hashCode())
+ ^ (value == null ? 0 : value.hashCode());
+ }
+ }
+}
Index: src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java
===================================================================
RCS file: src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java
diff -N src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,178 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Ovidio Mallo 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:
+ * Ovidio Mallo - initial API and implementation (bug 305367)
+ ******************************************************************************/
+
+package org.eclipse.core.internal.databinding.observable.masterdetail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.core.databinding.observable.IObserving;
+import org.eclipse.core.databinding.observable.IStaleListener;
+import org.eclipse.core.databinding.observable.ObservableTracker;
+import org.eclipse.core.databinding.observable.StaleEvent;
+import org.eclipse.core.databinding.observable.map.ComputedObservableMap;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
+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.internal.databinding.identity.IdentitySet;
+
+/**
+ * @since 1.4
+ */
+public class SetDetailValueObservableMap extends ComputedObservableMap
+ implements IObserving {
+
+ private IObservableFactory observableValueFactory;
+
+ private Map detailObservableValueMap = new HashMap();
+
+ private IdentitySet staleDetailObservables = new IdentitySet();
+
+ private IStaleListener detailStaleListener = new IStaleListener() {
+ public void handleStale(StaleEvent staleEvent) {
+ addStaleDetailObservable((IObservableValue) staleEvent
+ .getObservable());
+ }
+ };
+
+ /**
+ * @param masterKeySet
+ * @param observableValueFactory
+ * @param detailValueType
+ */
+ public SetDetailValueObservableMap(IObservableSet masterKeySet,
+ IObservableFactory observableValueFactory, Object detailValueType) {
+ super(masterKeySet, detailValueType);
+ this.observableValueFactory = observableValueFactory;
+ }
+
+ protected void hookListener(final Object addedKey) {
+ final IObservableValue detailValue = getDetailObservableValue(addedKey);
+
+ detailValue.addValueChangeListener(new IValueChangeListener() {
+ public void handleValueChange(ValueChangeEvent event) {
+ if (!event.getObservableValue().isStale()) {
+ staleDetailObservables.remove(detailValue);
+ }
+
+ fireSingleChange(addedKey, event.diff.getOldValue(),
+ event.diff.getNewValue());
+ }
+ });
+
+ detailValue.addStaleListener(detailStaleListener);
+ }
+
+ protected void unhookListener(Object removedKey) {
+ if (isDisposed()) {
+ return;
+ }
+
+ IObservableValue detailValue = (IObservableValue) detailObservableValueMap
+ .remove(removedKey);
+ staleDetailObservables.remove(detailValue);
+ detailValue.dispose();
+ }
+
+ private IObservableValue getDetailObservableValue(Object masterKey) {
+ IObservableValue detailValue = (IObservableValue) detailObservableValueMap
+ .get(masterKey);
+
+ if (detailValue == null) {
+ ObservableTracker.setIgnore(true);
+ try {
+ detailValue = (IObservableValue) observableValueFactory
+ .createObservable(masterKey);
+ } finally {
+ ObservableTracker.setIgnore(false);
+ }
+
+ detailObservableValueMap.put(masterKey, detailValue);
+
+ if (detailValue.isStale()) {
+ addStaleDetailObservable(detailValue);
+ }
+ }
+
+ return detailValue;
+ }
+
+ private void addStaleDetailObservable(IObservableValue detailObservable) {
+ boolean wasStale = isStale();
+ staleDetailObservables.add(detailObservable);
+ if (!wasStale) {
+ fireStale();
+ }
+ }
+
+ protected Object doGet(Object key) {
+ IObservableValue detailValue = getDetailObservableValue(key);
+ return detailValue.getValue();
+ }
+
+ protected Object doPut(Object key, Object value) {
+ IObservableValue detailValue = getDetailObservableValue(key);
+ Object oldValue = detailValue.getValue();
+ detailValue.setValue(value);
+ return oldValue;
+ }
+
+ public boolean containsKey(Object key) {
+ getterCalled();
+
+ return keySet().contains(key);
+ }
+
+ public Object remove(Object key) {
+ checkRealm();
+
+ if (!containsKey(key)) {
+ return null;
+ }
+
+ IObservableValue detailValue = getDetailObservableValue(key);
+ Object oldValue = detailValue.getValue();
+
+ keySet().remove(key);
+
+ return oldValue;
+ }
+
+ public int size() {
+ getterCalled();
+
+ return keySet().size();
+ }
+
+ public boolean isStale() {
+ return super.isStale() || staleDetailObservables != null
+ && !staleDetailObservables.isEmpty();
+ }
+
+ public Object getObserved() {
+ return keySet();
+ }
+
+ public synchronized void dispose() {
+ super.dispose();
+
+ observableValueFactory = null;
+ detailObservableValueMap = null;
+ detailStaleListener = null;
+ staleDetailObservables = null;
+ }
+
+ private void getterCalled() {
+ ObservableTracker.getterCalled(this);
+ }
+}
#P org.eclipse.core.databinding.property
Index: META-INF/MANIFEST.MF
===================================================================
RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.property/META-INF/MANIFEST.MF,v
retrieving revision 1.4
diff -u -r1.4 MANIFEST.MF
--- META-INF/MANIFEST.MF 10 Mar 2010 07:20:10 -0000 1.4
+++ META-INF/MANIFEST.MF 11 Dec 2010 01:48:12 -0000
@@ -2,7 +2,7 @@
Bundle-ManifestVersion: 2
Bundle-Name: %pluginName
Bundle-SymbolicName: org.eclipse.core.databinding.property
-Bundle-Version: 1.3.0.qualifier
+Bundle-Version: 1.4.0.qualifier
Bundle-ClassPath: .
Bundle-Vendor: %providerName
Bundle-Localization: plugin
Index: src/org/eclipse/core/databinding/property/value/ValueProperty.java
===================================================================
RCS file: /cvsroot/eclipse/org.eclipse.core.databinding.property/src/org/eclipse/core/databinding/property/value/ValueProperty.java,v
retrieving revision 1.7
diff -u -r1.7 ValueProperty.java
--- src/org/eclipse/core/databinding/property/value/ValueProperty.java 7 Dec 2010 16:14:40 -0000 1.7
+++ src/org/eclipse/core/databinding/property/value/ValueProperty.java 11 Dec 2010 01:48:12 -0000
@@ -8,14 +8,18 @@
* Contributors:
* Matthew Hall - initial API and implementation (bug 194734)
* Matthew Hall - bug 195222
+ * Ovidio Mallo - bug 305367
******************************************************************************/
package org.eclipse.core.databinding.property.value;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.map.IObservableMap;
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
+import org.eclipse.core.databinding.observable.set.IObservableSet;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.property.list.IListProperty;
import org.eclipse.core.databinding.property.map.IMapProperty;
@@ -108,8 +112,32 @@
}
public IObservableValue observeDetail(IObservableValue master) {
- return MasterDetailObservables.detailValue(master, valueFactory(master
- .getRealm()), getValueType());
+ return MasterDetailObservables.detailValue(master,
+ valueFactory(master.getRealm()), getValueType());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public IObservableList observeDetail(IObservableList master) {
+ return MasterDetailObservables.detailValues(master,
+ valueFactory(master.getRealm()), getValueType());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public IObservableMap observeDetail(IObservableSet master) {
+ return MasterDetailObservables.detailValues(master,
+ valueFactory(master.getRealm()), getValueType());
+ }
+
+ /**
+ * @since 1.4
+ */
+ public IObservableMap observeDetail(IObservableMap master) {
+ return MasterDetailObservables.detailValues(master,
+ valueFactory(master.getRealm()), getValueType());
}
public final IValueProperty value(IValueProperty detailValue) {
#P org.eclipse.jface.tests.databinding
Index: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java
===================================================================
RCS file: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java
diff -N src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,391 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Ovidio Mallo 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:
+ * Ovidio Mallo - initial API and implementation (bug 305367)
+ ******************************************************************************/
+
+package org.eclipse.core.tests.internal.databinding.observable.masterdetail;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.eclipse.core.databinding.beans.BeansObservables;
+import org.eclipse.core.databinding.observable.IObservable;
+import org.eclipse.core.databinding.observable.IObservableCollection;
+import org.eclipse.core.databinding.observable.Realm;
+import org.eclipse.core.databinding.observable.list.IObservableList;
+import org.eclipse.core.databinding.observable.list.ListDiff;
+import org.eclipse.core.databinding.observable.list.ListDiffEntry;
+import org.eclipse.core.databinding.observable.list.WritableList;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.value.WritableValue;
+import org.eclipse.core.internal.databinding.observable.masterdetail.ListDetailValueObservableList;
+import org.eclipse.jface.databinding.conformance.ObservableListContractTest;
+import org.eclipse.jface.databinding.conformance.delegate.AbstractObservableCollectionContractDelegate;
+import org.eclipse.jface.databinding.conformance.util.ListChangeEventTracker;
+import org.eclipse.jface.examples.databinding.model.SimplePerson;
+import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
+
+/**
+ * @since 1.3
+ */
+public class ListDetailValueObservableListTest extends
+ AbstractDefaultRealmTestCase {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite(
+ ListDetailValueObservableListTest.class.getName());
+ suite.addTestSuite(ListDetailValueObservableListTest.class);
+ suite.addTest(ObservableListContractTest.suite(new Delegate()));
+ return suite;
+ }
+
+ public void testUnmodifiability() {
+ WritableList masterObservableList = new WritableList();
+ masterObservableList.add(new SimplePerson());
+ masterObservableList.add(new SimplePerson());
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterObservableList, BeansObservables.valueFactory("name"),
+ null);
+
+ try {
+ ldol.add("name");
+ fail("ListDetailValueObservableList must not be modifiable.");
+ } catch (UnsupportedOperationException e) {
+ // expected exception
+ }
+
+ try {
+ ldol.remove(masterObservableList.get(0));
+ fail("ListDetailValueObservableList must not be modifiable.");
+ } catch (UnsupportedOperationException e) {
+ // expected exception
+ }
+
+ try {
+ ldol.removeAll(Collections.singleton(masterObservableList.get(0)));
+ fail("ListDetailValueObservableList must not be modifiable.");
+ } catch (UnsupportedOperationException e) {
+ // expected exception
+ }
+
+ try {
+ ldol.retainAll(Collections.EMPTY_LIST);
+ fail("ListDetailValueObservableList must not be modifiable.");
+ } catch (UnsupportedOperationException e) {
+ // expected exception
+ }
+
+ try {
+ ldol.move(0, 1);
+ fail("ListDetailValueObservableList must not be modifiable.");
+ } catch (UnsupportedOperationException e) {
+ // expected exception
+ }
+ }
+
+ public void testGetElementType() {
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ new WritableList(), BeansObservables.valueFactory("name"),
+ String.class);
+
+ assertSame(String.class, ldol.getElementType());
+ }
+
+ public void testGetObserved() {
+ WritableList masterList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, BeansObservables.valueFactory("name"), String.class);
+
+ // The observed object is the master list.
+ assertSame(masterList, ldol.getObserved());
+ }
+
+ public void testMasterListInitiallyNotEmpty() {
+ WritableList masterList = new WritableList();
+ SimplePerson person = new SimplePerson();
+ person.setName("name");
+ masterList.add(person);
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, BeansObservables.valueFactory("name"), String.class);
+
+ // Make sure that a non-empty master list is initialized correctly.
+ assertEquals(masterList.size(), ldol.size());
+ assertEquals(person.getName(), ldol.get(0));
+ }
+
+ public void testAddRemove() {
+ WritableList masterList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, BeansObservables.valueFactory("name"), String.class);
+
+ // Initially, the detail list is empty.
+ assertTrue(ldol.isEmpty());
+
+ // Add a first person and check that its name is in the detail list.
+ SimplePerson p1 = new SimplePerson();
+ p1.setName("name1");
+ masterList.add(p1);
+ assertEquals(masterList.size(), ldol.size());
+ assertEquals(p1.getName(), ldol.get(0));
+
+ // Add a second person and check that it's name is in the detail list.
+ SimplePerson p2 = new SimplePerson();
+ p2.setName("name2");
+ masterList.add(p2);
+ assertEquals(masterList.size(), ldol.size());
+ assertEquals(p2.getName(), ldol.get(1));
+
+ // Remove the first person from the master list and check that we still
+ // have the name of the second person in the detail list.
+ masterList.remove(0);
+ assertEquals(masterList.size(), ldol.size());
+ assertEquals(p2.getName(), ldol.get(0));
+
+ // Remove the second person as well.
+ masterList.remove(0);
+ assertTrue(ldol.isEmpty());
+ }
+
+ public void testChangeDetail() {
+ WritableList masterList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, BeansObservables.valueFactory("name"), String.class);
+
+ // Change the detail attribute explicitly.
+ SimplePerson p1 = new SimplePerson();
+ p1.setName("name1");
+ masterList.add(p1);
+ assertEquals(p1.getName(), ldol.get(0));
+ p1.setName("name2");
+ assertEquals(p1.getName(), ldol.get(0));
+
+ // Change the detail attribute by changing the master.
+ SimplePerson p2 = new SimplePerson();
+ p2.setName("name3");
+ masterList.set(0, p2);
+ assertEquals(p2.getName(), ldol.get(0));
+ }
+
+ public void testSet() {
+ WritableList masterList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, BeansObservables.valueFactory("name"), String.class);
+
+ // Change the detail attribute explicitly.
+ SimplePerson person = new SimplePerson();
+ person.setName("name1");
+ masterList.add(person);
+ assertEquals(person.getName(), ldol.get(0));
+
+ // Set a new name on the detail list.
+ ldol.set(0, "name2");
+ // Check that the name has been propagated to the master.
+ assertEquals("name2", person.getName());
+ assertEquals(person.getName(), ldol.get(0));
+ }
+
+ public void testDuplicateMasterElements() {
+ WritableList masterList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, BeansObservables.valueFactory("name"), String.class);
+
+ SimplePerson master = new SimplePerson();
+ master.setName("name1");
+
+ // Add the same master twice.
+ masterList.add(master);
+ masterList.add(master);
+
+ // Attach the change listener to the detail list.
+ ListChangeEventTracker changeTracker = ListChangeEventTracker
+ .observe(ldol);
+
+ // Setting the name on master should trigger an event on both
+ // occurrences of in the master list.
+ master.setName("name2");
+
+ // We should have 2 replace diffs, i.e. 4 diff entries.
+ assertEquals(1, changeTracker.count);
+ assertEquals(4, changeTracker.event.diff.getDifferences().length);
+ assertReplaceDiffAt(changeTracker.event.diff, 0, 0, "name1", "name2");
+ assertReplaceDiffAt(changeTracker.event.diff, 2, 0, "name1", "name2");
+
+ // Remove one instance of the master (one will remain).
+ masterList.remove(master);
+
+ // It should still be possible to work on the remaining master instance.
+ ldol.set(0, "name3");
+ assertEquals("name3", master.getName());
+ }
+
+ public void testDetailObservableChangeEvent() {
+ WritableList masterList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, BeansObservables.valueFactory("name"), String.class);
+
+ ListChangeEventTracker changeTracker = ListChangeEventTracker
+ .observe(ldol);
+
+ SimplePerson person = new SimplePerson();
+ person.setName("old name");
+
+ // Initially, we should not have received any event.
+ assertEquals(0, changeTracker.count);
+
+ // Add the person and check that we receive an addition event on the
+ // correct index and with the correct value.
+ masterList.add(person);
+ assertEquals(1, changeTracker.count);
+ assertEquals(1, changeTracker.event.diff.getDifferences().length);
+ assertTrue(changeTracker.event.diff.getDifferences()[0].isAddition());
+ assertEquals(0,
+ changeTracker.event.diff.getDifferences()[0].getPosition());
+ assertEquals(person.getName(),
+ changeTracker.event.diff.getDifferences()[0].getElement());
+
+ // Change the detail property and check that we receive a replace event.
+ person.setName("new name");
+ assertEquals(2, changeTracker.count);
+ assertIsSingleReplaceDiff(changeTracker.event.diff, 0, "old name",
+ "new name");
+ }
+
+ private void assertIsSingleReplaceDiff(ListDiff diff, int index,
+ Object oldElement, Object newElement) {
+ // We should have 2 diff entries.
+ assertEquals(2, diff.getDifferences().length);
+
+ // Check that it indeed is a replace diff.
+ assertReplaceDiffAt(diff, 0, index, oldElement, newElement);
+ }
+
+ private void assertReplaceDiffAt(ListDiff diff, int diffOffset, int index,
+ Object oldElement, Object newElement) {
+ ListDiffEntry entry1 = diff.getDifferences()[0];
+ ListDiffEntry entry2 = diff.getDifferences()[1];
+
+ // One diff entry must be an addition, the other a removal.
+ assertTrue(entry1.isAddition() != entry2.isAddition());
+
+ // Check for the index on the diff entries.
+ assertEquals(index, entry1.getPosition());
+ assertEquals(index, entry2.getPosition());
+
+ // Check for the old/new element values on both diff entries.
+ if (entry1.isAddition()) {
+ assertEquals(oldElement, entry2.getElement());
+ assertEquals(newElement, entry1.getElement());
+ } else {
+ assertEquals(oldElement, entry1.getElement());
+ assertEquals(newElement, entry2.getElement());
+ }
+ }
+
+ public void testMasterNull() {
+ WritableList masterObservableList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterObservableList, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Make sure null values are handled gracefully.
+ masterObservableList.add(null);
+ assertEquals(1, ldol.size());
+ assertNull(ldol.get(0));
+ }
+
+ public void testDetailObservableValuesAreDisposed() {
+ final List detailObservables = new ArrayList();
+ IObservableFactory detailValueFactory = new IObservableFactory() {
+ public IObservable createObservable(Object target) {
+ WritableValue detailObservable = new WritableValue();
+ // Remember the created observables.
+ detailObservables.add(detailObservable);
+ return detailObservable;
+ }
+ };
+
+ WritableList masterList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, detailValueFactory, null);
+
+ masterList.add(new Object());
+ masterList.add(new Object());
+
+ assertEquals(ldol.size(), detailObservables.size());
+
+ // No detail observables should be disposed yet.
+ assertFalse(((WritableValue) detailObservables.get(0)).isDisposed());
+ assertFalse(((WritableValue) detailObservables.get(1)).isDisposed());
+
+ // Only the detail observable for the removed master should be disposed.
+ masterList.remove(1);
+ assertFalse(((WritableValue) detailObservables.get(0)).isDisposed());
+ assertTrue(((WritableValue) detailObservables.get(1)).isDisposed());
+
+ // After disposing the detail list, all detail observables should be
+ // disposed.
+ ldol.dispose();
+ assertTrue(((WritableValue) detailObservables.get(0)).isDisposed());
+ assertTrue(((WritableValue) detailObservables.get(1)).isDisposed());
+ }
+
+ public void testDisposeOnMasterDisposed() {
+ WritableList masterList = new WritableList();
+ ListDetailValueObservableList ldol = new ListDetailValueObservableList(
+ masterList, BeansObservables.valueFactory("name"), String.class);
+
+ // Initially, nothing should be disposed.
+ assertFalse(masterList.isDisposed());
+ assertFalse(ldol.isDisposed());
+
+ // Upon disposing the master list, the detail list should be disposed as
+ // well.
+ masterList.dispose();
+ assertTrue(masterList.isDisposed());
+ assertTrue(ldol.isDisposed());
+ }
+
+ private static class Delegate extends
+ AbstractObservableCollectionContractDelegate {
+ public IObservableCollection createObservableCollection(Realm realm,
+ int elementCount) {
+ WritableList masterList = new WritableList(realm);
+ for (int i = 0; i < elementCount; i++) {
+ masterList.add(new SimplePerson());
+ }
+
+ return new TestListDetailValueObservableList(masterList,
+ BeansObservables.valueFactory(realm, "name"), String.class);
+ }
+
+ public void change(IObservable observable) {
+ TestListDetailValueObservableList ldol = (TestListDetailValueObservableList) observable;
+ ldol.masterList.add(new SimplePerson());
+ }
+
+ public Object getElementType(IObservableCollection collection) {
+ return String.class;
+ }
+ }
+
+ private static class TestListDetailValueObservableList extends
+ ListDetailValueObservableList {
+ final IObservableList masterList;
+
+ public TestListDetailValueObservableList(IObservableList masterList,
+ IObservableFactory detailValueFactory, Object detailType) {
+ super(masterList, detailValueFactory, detailType);
+ this.masterList = masterList;
+ }
+ }
+}
Index: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java
===================================================================
RCS file: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java
diff -N src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,309 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Ovidio Mallo 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:
+ * Ovidio Mallo - initial API and implementation (bug 305367)
+ ******************************************************************************/
+
+package org.eclipse.core.tests.internal.databinding.observable.masterdetail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.eclipse.core.databinding.beans.BeansObservables;
+import org.eclipse.core.databinding.observable.IObservable;
+import org.eclipse.core.databinding.observable.map.WritableMap;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.value.WritableValue;
+import org.eclipse.core.internal.databinding.observable.masterdetail.MapDetailValueObservableMap;
+import org.eclipse.jface.databinding.conformance.util.MapChangeEventTracker;
+import org.eclipse.jface.examples.databinding.model.SimplePerson;
+import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
+
+/**
+ * @since 1.3
+ */
+public class MapDetailValueObservableMapTest extends
+ AbstractDefaultRealmTestCase {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite(
+ MapDetailValueObservableMapTest.class.getName());
+ suite.addTestSuite(MapDetailValueObservableMapTest.class);
+ return suite;
+ }
+
+ public void testGetKeyType() {
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ new WritableMap(SimplePerson.class, SimplePerson.class),
+ BeansObservables.valueFactory("name"), String.class);
+
+ assertSame(SimplePerson.class, mdom.getKeyType());
+ }
+
+ public void testGetValueType() {
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ new WritableMap(), BeansObservables.valueFactory("name"),
+ String.class);
+
+ assertSame(String.class, mdom.getValueType());
+ }
+
+ public void testGetObserved() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // The observed object is the master key set.
+ assertSame(masterMap, mdom.getObserved());
+ }
+
+ public void testMasterSetInitiallyNotEmpty() {
+ WritableMap masterMap = new WritableMap();
+ SimplePerson person = new SimplePerson();
+ person.setName("name");
+ masterMap.put(person, person);
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // Make sure that a non-empty master key set is initialized correctly.
+ assertEquals(masterMap.size(), mdom.size());
+ assertEquals(person.getName(), mdom.get(person));
+ }
+
+ public void testAddRemove() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // Initially, the detail map is empty.
+ assertTrue(mdom.isEmpty());
+
+ // Add a first person and check that its name is in the detail map.
+ SimplePerson p1 = new SimplePerson();
+ p1.setName("name1");
+ masterMap.put(p1, p1);
+ assertEquals(masterMap.size(), mdom.size());
+ assertEquals(p1.getName(), mdom.get(p1));
+
+ // Add a second person and check that it's name is in the detail map.
+ SimplePerson p2 = new SimplePerson();
+ p2.setName("name2");
+ masterMap.put(p2, p2);
+ assertEquals(masterMap.size(), mdom.size());
+ assertEquals(p2.getName(), mdom.get(p2));
+
+ // Remove the first person from the master map and check that we still
+ // have the name of the second person in the detail map.
+ masterMap.remove(p1);
+ assertEquals(masterMap.size(), mdom.size());
+ assertEquals(p2.getName(), mdom.get(p2));
+
+ // Remove the second person as well.
+ masterMap.remove(p2);
+ assertTrue(mdom.isEmpty());
+ }
+
+ public void testChangeDetail() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // Change the detail attribute explicitly.
+ SimplePerson p1 = new SimplePerson();
+ p1.setName("name1");
+ masterMap.put(p1, p1);
+ assertEquals(p1.getName(), mdom.get(p1));
+ p1.setName("name2");
+ assertEquals(p1.getName(), mdom.get(p1));
+
+ // Change the detail attribute by changing the master.
+ SimplePerson p2 = new SimplePerson();
+ p2.setName("name3");
+ masterMap.put(p1, p2);
+ assertEquals(p2.getName(), mdom.get(p1));
+ }
+
+ public void testPut() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // Change the detail attribute explicitly.
+ SimplePerson person = new SimplePerson();
+ person.setName("name1");
+ masterMap.put(person, person);
+ assertEquals(person.getName(), mdom.get(person));
+
+ // Set a new name on the detail map.
+ mdom.put(person, "name2");
+ // Check that the name has been propagated to the master.
+ assertEquals("name2", person.getName());
+ assertEquals(person.getName(), mdom.get(person));
+ }
+
+ public void testContainsValue() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // Add a person with a given name.
+ SimplePerson person = new SimplePerson();
+ person.setName("name");
+ masterMap.put(person, person);
+
+ // Make sure the name of the person is contained.
+ assertTrue(mdom.containsValue(person.getName()));
+
+ // Remove the person and make sure that it's name cannot be found
+ // anymore.
+ masterMap.remove(person);
+ assertFalse(mdom.containsValue(person.getName()));
+ }
+
+ public void testRemove() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // Add two person objects to the map.
+ SimplePerson p1 = new SimplePerson();
+ SimplePerson p2 = new SimplePerson();
+ masterMap.put(p1, p1);
+ masterMap.put(p2, p2);
+
+ // Initially, both person objects should be contained in the detail map.
+ assertTrue(mdom.containsKey(p1));
+ assertTrue(mdom.containsKey(p2));
+
+ // Remove one person and check that it is not contained anymore.
+ mdom.remove(p1);
+ assertFalse(mdom.containsKey(p1));
+ assertTrue(mdom.containsKey(p2));
+
+ // Trying to remove a non-existent is allowed but has no effect.
+ mdom.remove(p1);
+ assertFalse(mdom.containsKey(p1));
+ assertTrue(mdom.containsKey(p2));
+ }
+
+ public void testDetailObservableChangeEvent() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ MapChangeEventTracker changeTracker = MapChangeEventTracker
+ .observe(mdom);
+
+ SimplePerson person = new SimplePerson();
+ person.setName("old name");
+
+ // Initially, we should not have received any event.
+ assertEquals(0, changeTracker.count);
+
+ // Add the person and check that we receive an addition event on the
+ // correct index and with the correct value.
+ masterMap.put(person, person);
+ assertEquals(1, changeTracker.count);
+ assertEquals(1, changeTracker.event.diff.getAddedKeys().size());
+ assertEquals(0, changeTracker.event.diff.getRemovedKeys().size());
+ assertEquals(0, changeTracker.event.diff.getChangedKeys().size());
+ assertSame(person, changeTracker.event.diff.getAddedKeys().iterator()
+ .next());
+ assertNull(changeTracker.event.diff.getOldValue(person));
+ assertEquals("old name", changeTracker.event.diff.getNewValue(person));
+
+ // Change the detail property and check that we receive a replace
+ person.setName("new name");
+ assertEquals(2, changeTracker.count);
+ assertEquals(0, changeTracker.event.diff.getAddedKeys().size());
+ assertEquals(0, changeTracker.event.diff.getRemovedKeys().size());
+ assertEquals(1, changeTracker.event.diff.getChangedKeys().size());
+ assertSame(person, changeTracker.event.diff.getChangedKeys().iterator()
+ .next());
+ assertEquals("old name", changeTracker.event.diff.getOldValue(person));
+ assertEquals("new name", changeTracker.event.diff.getNewValue(person));
+ }
+
+ public void testMasterNull() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // Make sure null values are handled gracefully.
+ masterMap.put(null, null);
+ assertEquals(1, mdom.size());
+ assertNull(mdom.get(null));
+ }
+
+ public void testDetailObservableValuesAreDisposed() {
+ final Map detailObservables = new HashMap();
+ IObservableFactory detailValueFactory = new IObservableFactory() {
+ public IObservable createObservable(Object target) {
+ WritableValue detailObservable = new WritableValue();
+ // Remember the created observables.
+ detailObservables.put(target, detailObservable);
+ return detailObservable;
+ }
+ };
+
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, detailValueFactory, null);
+
+ Object master1 = new Object();
+ Object master2 = new Object();
+ masterMap.put(master1, master1);
+ masterMap.put(master2, master2);
+
+ // Attach a listener in order to ensure that all detail observables are
+ // actually created.
+ MapChangeEventTracker.observe(mdom);
+
+ assertEquals(mdom.size(), detailObservables.size());
+
+ // No detail observables should be disposed yet.
+ assertFalse(((WritableValue) detailObservables.get(master1))
+ .isDisposed());
+ assertFalse(((WritableValue) detailObservables.get(master2))
+ .isDisposed());
+
+ // Only the detail observable for the removed master should be disposed.
+ masterMap.remove(master2);
+ assertFalse(((WritableValue) detailObservables.get(master1))
+ .isDisposed());
+ assertTrue(((WritableValue) detailObservables.get(master2))
+ .isDisposed());
+
+ // After disposing the detail map, all detail observables should be
+ // disposed.
+ mdom.dispose();
+ assertTrue(((WritableValue) detailObservables.get(master1))
+ .isDisposed());
+ assertTrue(((WritableValue) detailObservables.get(master2))
+ .isDisposed());
+ }
+
+ public void testDisposeOnMasterDisposed() {
+ WritableMap masterMap = new WritableMap();
+ MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
+ masterMap, BeansObservables.valueFactory("name"), String.class);
+
+ // Initially, nothing should be disposed.
+ assertFalse(masterMap.isDisposed());
+ assertFalse(mdom.isDisposed());
+
+ // Upon disposing the master map, the detail map should be disposed as
+ // well.
+ masterMap.dispose();
+ assertTrue(masterMap.isDisposed());
+ assertTrue(mdom.isDisposed());
+ }
+}
Index: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java
===================================================================
RCS file: src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java
diff -N src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java
--- /dev/null 1 Jan 1970 00:00:00 -0000
+++ src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java 1 Jan 1970 00:00:00 -0000
@@ -0,0 +1,311 @@
+/*******************************************************************************
+ * Copyright (c) 2010 Ovidio Mallo 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:
+ * Ovidio Mallo - initial API and implementation (bug 305367)
+ ******************************************************************************/
+
+package org.eclipse.core.tests.internal.databinding.observable.masterdetail;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import junit.framework.Test;
+import junit.framework.TestSuite;
+
+import org.eclipse.core.databinding.beans.BeansObservables;
+import org.eclipse.core.databinding.observable.IObservable;
+import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
+import org.eclipse.core.databinding.observable.set.WritableSet;
+import org.eclipse.core.databinding.observable.value.WritableValue;
+import org.eclipse.core.internal.databinding.observable.masterdetail.SetDetailValueObservableMap;
+import org.eclipse.jface.databinding.conformance.util.MapChangeEventTracker;
+import org.eclipse.jface.examples.databinding.model.SimplePerson;
+import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
+
+/**
+ * @since 1.3
+ */
+public class SetDetailValueObservableMapTest extends
+ AbstractDefaultRealmTestCase {
+
+ public static Test suite() {
+ TestSuite suite = new TestSuite(SetDetailValueObservableMapTest.class
+ .getName());
+ suite.addTestSuite(SetDetailValueObservableMapTest.class);
+ return suite;
+ }
+
+ public void testGetValueType() {
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ new WritableSet(), BeansObservables.valueFactory("name"),
+ String.class);
+
+ assertSame(String.class, sdom.getValueType());
+ }
+
+ public void testGetObserved() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // The observed object is the master key set.
+ assertSame(masterKeySet, sdom.getObserved());
+ }
+
+ public void testMasterSetInitiallyNotEmpty() {
+ WritableSet masterKeySet = new WritableSet();
+ SimplePerson person = new SimplePerson();
+ person.setName("name");
+ masterKeySet.add(person);
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Make sure that a non-empty master key set is initialized correctly.
+ assertEquals(masterKeySet.size(), sdom.size());
+ assertEquals(person.getName(), sdom.get(person));
+ }
+
+ public void testAddRemove() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Initially, the detail map is empty.
+ assertTrue(sdom.isEmpty());
+
+ // Add a first person and check that its name is in the detail list.
+ SimplePerson p1 = new SimplePerson();
+ p1.setName("name1");
+ masterKeySet.add(p1);
+ assertEquals(masterKeySet.size(), sdom.size());
+ assertEquals(p1.getName(), sdom.get(p1));
+
+ // Add a second person and check that it's name is in the detail list.
+ SimplePerson p2 = new SimplePerson();
+ p2.setName("name2");
+ masterKeySet.add(p2);
+ assertEquals(masterKeySet.size(), sdom.size());
+ assertEquals(p2.getName(), sdom.get(p2));
+
+ // Remove the first person from the master list and check that we still
+ // have the name of the second person in the detail list.
+ masterKeySet.remove(p1);
+ assertEquals(masterKeySet.size(), sdom.size());
+ assertEquals(p2.getName(), sdom.get(p2));
+
+ // Remove the second person as well.
+ masterKeySet.remove(p2);
+ assertTrue(sdom.isEmpty());
+ }
+
+ public void testChangeDetail() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Change the detail attribute explicitly.
+ SimplePerson p1 = new SimplePerson();
+ p1.setName("name1");
+ masterKeySet.add(p1);
+ assertEquals(p1.getName(), sdom.get(p1));
+ p1.setName("name2");
+ assertEquals(p1.getName(), sdom.get(p1));
+
+ // Change the detail attribute by changing the master.
+ SimplePerson p2 = new SimplePerson();
+ p2.setName("name3");
+ masterKeySet.add(p2);
+ assertEquals(p2.getName(), sdom.get(p2));
+ }
+
+ public void testPut() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Change the detail attribute explicitly.
+ SimplePerson person = new SimplePerson();
+ person.setName("name1");
+ masterKeySet.add(person);
+ assertEquals(person.getName(), sdom.get(person));
+
+ // Set a new name on the detail map.
+ sdom.put(person, "name2");
+ // Check that the name has been propagated to the master.
+ assertEquals("name2", person.getName());
+ assertEquals(person.getName(), sdom.get(person));
+ }
+
+ public void testContainsValue() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Add a person with a given name.
+ SimplePerson person = new SimplePerson();
+ person.setName("name");
+ masterKeySet.add(person);
+
+ // Make sure the name of the person is contained.
+ assertTrue(sdom.containsValue(person.getName()));
+
+ // Remove the person and make sure that it's name cannot be found
+ // anymore.
+ masterKeySet.remove(person);
+ assertFalse(sdom.containsValue(person.getName()));
+ }
+
+ public void testRemove() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Add two person objects to the map.
+ SimplePerson p1 = new SimplePerson();
+ SimplePerson p2 = new SimplePerson();
+ masterKeySet.add(p1);
+ masterKeySet.add(p2);
+
+ // Initially, both person objects should be contained in the detail map.
+ assertTrue(sdom.containsKey(p1));
+ assertTrue(sdom.containsKey(p2));
+
+ // Remove one person and check that it is not contained anymore.
+ sdom.remove(p1);
+ assertFalse(sdom.containsKey(p1));
+ assertTrue(sdom.containsKey(p2));
+
+ // Trying to remove a non-existent is allowed but has no effect.
+ sdom.remove(p1);
+ assertFalse(sdom.containsKey(p1));
+ assertTrue(sdom.containsKey(p2));
+ }
+
+ public void testDetailObservableChangeEvent() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ MapChangeEventTracker changeTracker = MapChangeEventTracker
+ .observe(sdom);
+
+ SimplePerson person = new SimplePerson();
+ person.setName("old name");
+
+ // Initially, we should not have received any event.
+ assertEquals(0, changeTracker.count);
+
+ // Add the person and check that we receive an addition event on the
+ // correct index and with the correct value.
+ masterKeySet.add(person);
+ assertEquals(1, changeTracker.count);
+ assertEquals(1, changeTracker.event.diff.getAddedKeys().size());
+ assertEquals(0, changeTracker.event.diff.getRemovedKeys().size());
+ assertEquals(0, changeTracker.event.diff.getChangedKeys().size());
+ assertSame(person, changeTracker.event.diff.getAddedKeys().iterator()
+ .next());
+ assertNull(changeTracker.event.diff.getOldValue(person));
+ assertEquals("old name", changeTracker.event.diff.getNewValue(person));
+
+ // Change the detail property and check that we receive a replace
+ person.setName("new name");
+ assertEquals(2, changeTracker.count);
+ assertEquals(0, changeTracker.event.diff.getAddedKeys().size());
+ assertEquals(0, changeTracker.event.diff.getRemovedKeys().size());
+ assertEquals(1, changeTracker.event.diff.getChangedKeys().size());
+ assertSame(person, changeTracker.event.diff.getChangedKeys().iterator()
+ .next());
+ assertEquals("old name", changeTracker.event.diff.getOldValue(person));
+ assertEquals("new name", changeTracker.event.diff.getNewValue(person));
+ }
+
+ public void testMasterNull() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Make sure null values are handled gracefully.
+ masterKeySet.add(null);
+ assertEquals(1, sdom.size());
+ assertNull(sdom.get(null));
+ }
+
+ public void testDetailObservableValuesAreDisposed() {
+ final Map detailObservables = new HashMap();
+ IObservableFactory detailValueFactory = new IObservableFactory() {
+ public IObservable createObservable(Object target) {
+ WritableValue detailObservable = new WritableValue();
+ // Remember the created observables.
+ detailObservables.put(target, detailObservable);
+ return detailObservable;
+ }
+ };
+
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, detailValueFactory, null);
+
+ Object master1 = new Object();
+ Object master2 = new Object();
+ masterKeySet.add(master1);
+ masterKeySet.add(master2);
+
+ // Attach a listener in order to ensure that all detail observables are
+ // actually created.
+ MapChangeEventTracker.observe(sdom);
+
+ assertEquals(sdom.size(), detailObservables.size());
+
+ // No detail observables should be disposed yet.
+ assertFalse(((WritableValue) detailObservables.get(master1))
+ .isDisposed());
+ assertFalse(((WritableValue) detailObservables.get(master2))
+ .isDisposed());
+
+ // Only the detail observable for the removed master should be disposed.
+ masterKeySet.remove(master2);
+ assertFalse(((WritableValue) detailObservables.get(master1))
+ .isDisposed());
+ assertTrue(((WritableValue) detailObservables.get(master2))
+ .isDisposed());
+
+ // After disposing the detail map, all detail observables should be
+ // disposed.
+ sdom.dispose();
+ assertTrue(((WritableValue) detailObservables.get(master1))
+ .isDisposed());
+ assertTrue(((WritableValue) detailObservables.get(master2))
+ .isDisposed());
+ }
+
+ public void testDisposeOnMasterDisposed() {
+ WritableSet masterKeySet = new WritableSet();
+ SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
+ masterKeySet, BeansObservables.valueFactory("name"),
+ String.class);
+
+ // Initially, nothing should be disposed.
+ assertFalse(masterKeySet.isDisposed());
+ assertFalse(sdom.isDisposed());
+
+ // Upon disposing the master list, the detail list should be disposed as
+ // well.
+ masterKeySet.dispose();
+ assertTrue(masterKeySet.isDisposed());
+ assertTrue(sdom.isDisposed());
+ }
+}
Index: src/org/eclipse/jface/tests/databinding/BindingTestSuite.java
===================================================================
RCS file: /cvsroot/eclipse/org.eclipse.jface.tests.databinding/src/org/eclipse/jface/tests/databinding/BindingTestSuite.java,v
retrieving revision 1.125
diff -u -r1.125 BindingTestSuite.java
--- src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 7 Dec 2010 16:13:54 -0000 1.125
+++ src/org/eclipse/jface/tests/databinding/BindingTestSuite.java 11 Dec 2010 01:48:13 -0000
@@ -17,7 +17,7 @@
* 246103, 249992, 256150, 256543, 262269, 175735, 262946,
* 255734, 263693, 169876, 266038, 268336, 270461, 271720,
* 283204, 281723, 283428
- * Ovidio Mallo - bugs 237163, 235195, 299619
+ * Ovidio Mallo - bugs 237163, 235195, 299619, 305367
*******************************************************************************/
package org.eclipse.jface.tests.databinding;
@@ -142,6 +142,9 @@
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableMapTest;
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableSetTest;
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableValueTest;
+import org.eclipse.core.tests.internal.databinding.observable.masterdetail.ListDetailValueObservableListTest;
+import org.eclipse.core.tests.internal.databinding.observable.masterdetail.MapDetailValueObservableMapTest;
+import org.eclipse.core.tests.internal.databinding.observable.masterdetail.SetDetailValueObservableMapTest;
import org.eclipse.core.tests.internal.databinding.property.value.ListSimpleValueObservableListTest;
import org.eclipse.core.tests.internal.databinding.property.value.MapSimpleValueObservableMapTest;
import org.eclipse.core.tests.internal.databinding.property.value.SetSimpleValueObservableMapTest;
@@ -384,6 +387,9 @@
addTestSuite(DetailObservableMapTest.class);
addTest(DetailObservableSetTest.suite());
addTest(DetailObservableValueTest.suite());
+ addTest(ListDetailValueObservableListTest.suite());
+ addTest(MapDetailValueObservableMapTest.suite());
+ addTest(SetDetailValueObservableMapTest.suite());
// org.eclipse.core.tests.internal.databinding.property.value
addTestSuite(MapSimpleValueObservableMapTest.class);