### 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);