View | Details | Raw Unified | Return to bug 305367 | Differences between
and this patch

Collapse All | Expand All

(-)META-INF/MANIFEST.MF (-1 / +1 lines)
Lines 2-8 Link Here
2
Bundle-ManifestVersion: 2
2
Bundle-ManifestVersion: 2
3
Bundle-Name: %pluginName
3
Bundle-Name: %pluginName
4
Bundle-SymbolicName: org.eclipse.core.databinding.observable
4
Bundle-SymbolicName: org.eclipse.core.databinding.observable
5
Bundle-Version: 1.3.0.qualifier
5
Bundle-Version: 1.4.0.qualifier
6
Bundle-ClassPath: .
6
Bundle-ClassPath: .
7
Bundle-Vendor: %providerName
7
Bundle-Vendor: %providerName
8
Bundle-Localization: plugin
8
Bundle-Localization: plugin
(-)src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java (-1 / +120 lines)
Lines 9-14 Link Here
9
 *     IBM Corporation - initial API and implementation
9
 *     IBM Corporation - initial API and implementation
10
 *     Brad Reynolds - bug 147515
10
 *     Brad Reynolds - bug 147515
11
 *     Matthew Hall - bug 221704, 226289
11
 *     Matthew Hall - bug 221704, 226289
12
 *     Ovidio Mallo - bugs 305367
12
 *******************************************************************************/
13
 *******************************************************************************/
13
14
14
package org.eclipse.core.databinding.observable.masterdetail;
15
package org.eclipse.core.databinding.observable.masterdetail;
Lines 21-26 Link Here
21
import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableMap;
22
import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableMap;
22
import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableSet;
23
import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableSet;
23
import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableValue;
24
import org.eclipse.core.internal.databinding.observable.masterdetail.DetailObservableValue;
25
import org.eclipse.core.internal.databinding.observable.masterdetail.ListDetailValueObservableList;
26
import org.eclipse.core.internal.databinding.observable.masterdetail.MapDetailValueObservableMap;
27
import org.eclipse.core.internal.databinding.observable.masterdetail.SetDetailValueObservableMap;
24
28
25
/**
29
/**
26
 * Allows for the observation of an attribute, the detail, of an observable
30
 * Allows for the observation of an attribute, the detail, of an observable
Lines 29-35 Link Here
29
 * @since 1.0
33
 * @since 1.0
30
 */
34
 */
31
public class MasterDetailObservables {
35
public class MasterDetailObservables {
32
	
36
33
	/**
37
	/**
34
	 * Creates a detail observable value from a master observable value and a
38
	 * Creates a detail observable value from a master observable value and a
35
	 * factory. This can be used to create observable values that represent a
39
	 * factory. This can be used to create observable values that represent a
Lines 145-148 Link Here
145
		return new DetailObservableMap(detailFactory, master, detailKeyType,
149
		return new DetailObservableMap(detailFactory, master, detailKeyType,
146
				detailValueType);
150
				detailValueType);
147
	}
151
	}
152
153
	/**
154
	 * Returns a detail observable list where each element is the detail value
155
	 * of the element in the master observable list. The provided factory is
156
	 * used to create the detail observable values for every master element
157
	 * which then define the elements of the detail list. The detail list
158
	 * resides in the same realm as the given master list.
159
	 * 
160
	 * <p>
161
	 * Note that since the values of the returned list are detail values of the
162
	 * elements of the master list, the only modifications supported are through
163
	 * the {@link IObservableList#set(int, Object)} method. Modifications made
164
	 * through the returned list are made through the detail observables created
165
	 * by the specified observable factory.
166
	 * </p>
167
	 * 
168
	 * @param masterList
169
	 *            The master observable list.
170
	 * @param detailFactory
171
	 *            The factory for creating {@link IObservableValue} instances
172
	 *            for the elements of the master list which then define the
173
	 *            elements of the new detail list.
174
	 * @param detailType
175
	 *            The value type of the detail values, typically of type
176
	 *            <code>java.lang.Class</code>. May be <code>null</code>.
177
	 * @return A detail observable list with elements which correspond to the
178
	 *         detail values of the elements of the master list.
179
	 * 
180
	 * @since 1.4
181
	 */
182
	public static IObservableList detailValues(IObservableList masterList,
183
			IObservableFactory detailFactory, Object detailType) {
184
		return new ListDetailValueObservableList(masterList, detailFactory,
185
				detailType);
186
	}
187
188
	/**
189
	 * Returns a detail observable map where the map's key set is the same as
190
	 * the given observable set, and where each value is the detail value of the
191
	 * element in the master observable set. The provided factory is used to
192
	 * create the detail observable values for every master key which then
193
	 * define the values of the detail map. The detail map resides in the same
194
	 * realm as the given master set.
195
	 * 
196
	 * <p>
197
	 * Note that since the values of the returned map are detail values of the
198
	 * elements of the master set, the only modifications supported are through
199
	 * the {@link IObservableMap#put(Object, Object)} and
200
	 * {@link IObservableMap#putAll(java.util.Map)} methods. Therefore, the
201
	 * returned map does not add entries for elements not already contained in
202
	 * the master set. Modifications made through the returned detail map are
203
	 * made through the detail observables created by the specified observable
204
	 * factory.
205
	 * </p>
206
	 * 
207
	 * @param masterSet
208
	 *            The master observable set.
209
	 * @param detailFactory
210
	 *            The factory for creating {@link IObservableValue} instances
211
	 *            for the elements of the master set which then define the
212
	 *            values of the new detail map.
213
	 * @param detailType
214
	 *            The value type of the detail values, typically of type
215
	 *            <code>java.lang.Class</code>. May be <code>null</code>.
216
	 * @return A detail observable map with the given master set as key set and
217
	 *         with values which correspond to the detail values of the elements
218
	 *         of the master set.
219
	 * 
220
	 * @since 1.4
221
	 */
222
	public static IObservableMap detailValues(IObservableSet masterSet,
223
			IObservableFactory detailFactory, Object detailType) {
224
		return new SetDetailValueObservableMap(masterSet, detailFactory,
225
				detailType);
226
	}
227
228
	/**
229
	 * Returns a detail observable map where the map's key set is the same as
230
	 * the one of the given master observable map, and where each value is the
231
	 * detail value of the corresponding value in the master observable map. The
232
	 * provided factory is used to create the detail observable values for every
233
	 * master value which then define the values of the detail map. The detail
234
	 * map resides in the same realm as the given master map.
235
	 * 
236
	 * <p>
237
	 * Note that since the values of the returned map are detail values of the
238
	 * values of the master map, the only modifications supported are through
239
	 * the {@link IObservableMap#put(Object, Object)} and
240
	 * {@link IObservableMap#putAll(java.util.Map)} methods. Therefore, the
241
	 * returned map does not add entries for keys not already contained in the
242
	 * master map's key set. Modifications made through the returned detail map
243
	 * are made through the detail observables created by the specified
244
	 * observable factory.
245
	 * </p>
246
	 * 
247
	 * @param masterMap
248
	 *            The master observable map.
249
	 * @param detailFactory
250
	 *            The factory for creating {@link IObservableValue} instances
251
	 *            for the values of the master map which then define the values
252
	 *            of the new detail map.
253
	 * @param detailType
254
	 *            The value type of the detail values, typically of type
255
	 *            <code>java.lang.Class</code>. May be <code>null</code>.
256
	 * @return A detail observable map with the same key set as the given master
257
	 *         observable map and with values which correspond to the detail
258
	 *         values of the values of the master map.
259
	 * 
260
	 * @since 1.4
261
	 */
262
	public static IObservableMap detailValues(IObservableMap masterMap,
263
			IObservableFactory detailFactory, Object detailType) {
264
		return new MapDetailValueObservableMap(masterMap, detailFactory,
265
				detailType);
266
	}
148
}
267
}
(-)src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java (+351 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Ovidio Mallo and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Ovidio Mallo - initial API and implementation (bug 305367)
10
 ******************************************************************************/
11
12
package org.eclipse.core.internal.databinding.observable.masterdetail;
13
14
import java.util.ArrayList;
15
import java.util.BitSet;
16
import java.util.Collection;
17
import java.util.Collections;
18
import java.util.Iterator;
19
import java.util.RandomAccess;
20
21
import org.eclipse.core.databinding.observable.Diffs;
22
import org.eclipse.core.databinding.observable.DisposeEvent;
23
import org.eclipse.core.databinding.observable.IDisposeListener;
24
import org.eclipse.core.databinding.observable.IObserving;
25
import org.eclipse.core.databinding.observable.IStaleListener;
26
import org.eclipse.core.databinding.observable.ObservableTracker;
27
import org.eclipse.core.databinding.observable.StaleEvent;
28
import org.eclipse.core.databinding.observable.list.AbstractObservableList;
29
import org.eclipse.core.databinding.observable.list.IListChangeListener;
30
import org.eclipse.core.databinding.observable.list.IObservableList;
31
import org.eclipse.core.databinding.observable.list.ListChangeEvent;
32
import org.eclipse.core.databinding.observable.list.ListDiff;
33
import org.eclipse.core.databinding.observable.list.ListDiffEntry;
34
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
35
import org.eclipse.core.databinding.observable.value.IObservableValue;
36
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
37
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
38
import org.eclipse.core.internal.databinding.identity.IdentityMap;
39
import org.eclipse.core.internal.databinding.identity.IdentitySet;
40
41
/**
42
 * @since 1.4
43
 */
44
public class ListDetailValueObservableList extends AbstractObservableList
45
		implements IObserving, RandomAccess {
46
47
	private IObservableList masterList;
48
49
	private IObservableFactory detailFactory;
50
51
	private Object detailType;
52
53
	// The list of detail observables.
54
	private ArrayList detailList;
55
56
	// Maps every master to a DetailEntry containing the detail observable. This
57
	// map is used to avoid that multiple detail observables are created for the
58
	// same master.
59
	private IdentityMap masterDetailMap = new IdentityMap();
60
61
	private IdentitySet staleDetailObservables = new IdentitySet();
62
63
	private IListChangeListener masterListListener = new IListChangeListener() {
64
		public void handleListChange(ListChangeEvent event) {
65
			handleMasterListChange(event.diff);
66
		}
67
	};
68
69
	private IValueChangeListener detailValueListener = new IValueChangeListener() {
70
		public void handleValueChange(ValueChangeEvent event) {
71
			if (!event.getObservable().isStale()) {
72
				staleDetailObservables.remove(event.getObservable());
73
			}
74
			handleDetailValueChange(event);
75
		}
76
	};
77
78
	private IStaleListener masterStaleListener = new IStaleListener() {
79
		public void handleStale(StaleEvent staleEvent) {
80
			fireStale();
81
		}
82
	};
83
84
	private IStaleListener detailStaleListener = new IStaleListener() {
85
		public void handleStale(StaleEvent staleEvent) {
86
			boolean wasStale = isStale();
87
			staleDetailObservables.add((staleEvent.getObservable()));
88
			if (!wasStale) {
89
				fireStale();
90
			}
91
		}
92
	};
93
94
	/**
95
	 * 
96
	 * @param masterList
97
	 * @param detailFactory
98
	 * @param detailType
99
	 */
100
	public ListDetailValueObservableList(IObservableList masterList,
101
			IObservableFactory detailFactory, Object detailType) {
102
		super(masterList.getRealm());
103
		this.masterList = masterList;
104
		this.detailFactory = detailFactory;
105
		this.detailType = detailType;
106
		this.detailList = new ArrayList();
107
108
		// Add change/stale/dispose listeners on the master list.
109
		masterList.addListChangeListener(masterListListener);
110
		masterList.addStaleListener(masterStaleListener);
111
		masterList.addDisposeListener(new IDisposeListener() {
112
			public void handleDispose(DisposeEvent event) {
113
				ListDetailValueObservableList.this.dispose();
114
			}
115
		});
116
117
		ListDiff initMasterDiff = Diffs.computeListDiff(Collections.EMPTY_LIST,
118
				masterList);
119
		handleMasterListChange(initMasterDiff);
120
	}
121
122
	protected synchronized void firstListenerAdded() {
123
		for (int i = 0; i < detailList.size(); i++) {
124
			IObservableValue detail = (IObservableValue) detailList.get(i);
125
			detail.addValueChangeListener(detailValueListener);
126
			detail.addStaleListener(detailStaleListener);
127
			if (detail.isStale()) {
128
				staleDetailObservables.add(detail);
129
			}
130
		}
131
	}
132
133
	protected synchronized void lastListenerRemoved() {
134
		if (isDisposed()) {
135
			return;
136
		}
137
138
		for (int i = 0; i < detailList.size(); i++) {
139
			IObservableValue detail = (IObservableValue) detailList.get(i);
140
			detail.removeValueChangeListener(detailValueListener);
141
			detail.removeStaleListener(detailStaleListener);
142
		}
143
		staleDetailObservables.clear();
144
	}
145
146
	private void handleMasterListChange(ListDiff masterListDiff) {
147
		boolean wasStale = isStale();
148
149
		boolean hasListeners = hasListeners();
150
		ListDiffEntry[] masterEntries = masterListDiff.getDifferences();
151
		ListDiffEntry[] detailEntries = new ListDiffEntry[masterEntries.length];
152
		for (int i = 0; i < masterEntries.length; i++) {
153
			ListDiffEntry masterEntry = masterEntries[i];
154
			int index = masterEntry.getPosition();
155
156
			Object masterElement = masterEntry.getElement();
157
			Object detailValue;
158
			if (masterEntry.isAddition()) {
159
				detailValue = addDetailObservable(masterElement, index);
160
			} else {
161
				detailValue = removeDetailObservable(masterElement, index);
162
			}
163
164
			if (hasListeners) {
165
				// Create the corresponding diff for the detail list.
166
				detailEntries[i] = Diffs.createListDiffEntry(index,
167
						masterEntry.isAddition(), detailValue);
168
			}
169
		}
170
171
		if (hasListeners) {
172
			if (!wasStale && isStale()) {
173
				fireStale();
174
			}
175
176
			// Fire a list change event with the adapted diff.
177
			fireListChange(Diffs.createListDiff(detailEntries));
178
		}
179
	}
180
181
	private Object addDetailObservable(Object masterElement, int index) {
182
		DetailEntry detailEntry = (DetailEntry) masterDetailMap
183
				.get(masterElement);
184
		if (detailEntry != null) {
185
			// If we already have a detail observable for the given
186
			// masterElement, we increment the reference count.
187
			detailEntry.masterReferenceCount++;
188
			detailList.add(index, detailEntry.detailObservable);
189
			return detailEntry.detailObservable.getValue();
190
		}
191
192
		IObservableValue detail = createDetailObservable(masterElement);
193
		masterDetailMap.put(masterElement, new DetailEntry(detail));
194
195
		detailList.add(index, detail);
196
197
		if (hasListeners()) {
198
			detail.addValueChangeListener(detailValueListener);
199
			detail.addStaleListener(detailStaleListener);
200
			if (detail.isStale()) {
201
				staleDetailObservables.add(detail);
202
			}
203
		}
204
205
		return detail.getValue();
206
	}
207
208
	private Object removeDetailObservable(Object masterElement, int index) {
209
		IObservableValue detail = (IObservableValue) detailList.remove(index);
210
		Object detailValue = detail.getValue();
211
212
		DetailEntry detailEntry = (DetailEntry) masterDetailMap
213
				.get(masterElement);
214
215
		// We may only dispose the detail observable ASA there are no more
216
		// masters referencing it.
217
		detailEntry.masterReferenceCount--;
218
		if (detailEntry.masterReferenceCount == 0) {
219
			masterDetailMap.remove(masterElement);
220
			staleDetailObservables.remove(detail);
221
			detail.dispose();
222
		}
223
224
		return detailValue;
225
	}
226
227
	private void handleDetailValueChange(ValueChangeEvent event) {
228
		IObservableValue detail = event.getObservableValue();
229
230
		// When we get a change event on a detail observable, we must find its
231
		// position while there may also be duplicate entries.
232
		BitSet detailIndexes = new BitSet();
233
		for (int i = 0; i < detailList.size(); i++) {
234
			if (detailList.get(i) == detail) {
235
				detailIndexes.set(i);
236
			}
237
		}
238
239
		// Create the diff for every found position.
240
		Object oldValue = event.diff.getOldValue();
241
		Object newValue = event.diff.getNewValue();
242
		ListDiffEntry[] diffEntries = new ListDiffEntry[2 * detailIndexes
243
				.cardinality()];
244
		int diffIndex = 0;
245
		for (int b = detailIndexes.nextSetBit(0); b != -1; b = detailIndexes
246
				.nextSetBit(b + 1)) {
247
			diffEntries[diffIndex++] = Diffs.createListDiffEntry(b, false,
248
					oldValue);
249
			diffEntries[diffIndex++] = Diffs.createListDiffEntry(b, true,
250
					newValue);
251
		}
252
		fireListChange(Diffs.createListDiff(diffEntries));
253
	}
254
255
	private IObservableValue createDetailObservable(Object masterElement) {
256
		ObservableTracker.setIgnore(true);
257
		try {
258
			return (IObservableValue) detailFactory
259
					.createObservable(masterElement);
260
		} finally {
261
			ObservableTracker.setIgnore(false);
262
		}
263
	}
264
265
	protected int doGetSize() {
266
		return detailList.size();
267
	}
268
269
	public Object get(int index) {
270
		ObservableTracker.getterCalled(this);
271
		return ((IObservableValue) detailList.get(index)).getValue();
272
	}
273
274
	public Object set(int index, Object element) {
275
		IObservableValue detail = (IObservableValue) detailList.get(index);
276
		Object oldElement = detail.getValue();
277
		detail.setValue(element);
278
		return oldElement;
279
	}
280
281
	public Object move(int oldIndex, int newIndex) {
282
		throw new UnsupportedOperationException();
283
	}
284
285
	public boolean remove(Object o) {
286
		throw new UnsupportedOperationException();
287
	}
288
289
	public boolean removeAll(Collection c) {
290
		throw new UnsupportedOperationException();
291
	}
292
293
	public boolean retainAll(Collection c) {
294
		throw new UnsupportedOperationException();
295
	}
296
297
	public void clear() {
298
		throw new UnsupportedOperationException();
299
	}
300
301
	public Object getElementType() {
302
		return detailType;
303
	}
304
305
	public boolean isStale() {
306
		return super.isStale()
307
				|| (masterList != null && masterList.isStale())
308
				|| (staleDetailObservables != null && !staleDetailObservables
309
						.isEmpty());
310
	}
311
312
	public Object getObserved() {
313
		return masterList;
314
	}
315
316
	public synchronized void dispose() {
317
		if (masterList != null) {
318
			masterList.removeListChangeListener(masterListListener);
319
			masterList.removeStaleListener(masterStaleListener);
320
		}
321
322
		if (detailList != null) {
323
			for (Iterator iter = detailList.iterator(); iter.hasNext();) {
324
				IObservableValue detailValue = (IObservableValue) iter.next();
325
				detailValue.dispose();
326
			}
327
			detailList.clear();
328
		}
329
330
		masterList = null;
331
		detailFactory = null;
332
		detailType = null;
333
		masterListListener = null;
334
		detailValueListener = null;
335
		masterDetailMap = null;
336
		staleDetailObservables = null;
337
338
		super.dispose();
339
	}
340
341
	private static final class DetailEntry {
342
343
		private final IObservableValue detailObservable;
344
345
		private int masterReferenceCount = 1;
346
347
		public DetailEntry(IObservableValue detailObservable) {
348
			this.detailObservable = detailObservable;
349
		}
350
	}
351
}
(-)src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java (+405 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Ovidio Mallo and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Ovidio Mallo - initial API and implementation (bug 305367)
10
 ******************************************************************************/
11
12
package org.eclipse.core.internal.databinding.observable.masterdetail;
13
14
import java.util.AbstractSet;
15
import java.util.Collections;
16
import java.util.IdentityHashMap;
17
import java.util.Iterator;
18
import java.util.Map;
19
import java.util.Set;
20
21
import org.eclipse.core.databinding.observable.Diffs;
22
import org.eclipse.core.databinding.observable.DisposeEvent;
23
import org.eclipse.core.databinding.observable.IDisposeListener;
24
import org.eclipse.core.databinding.observable.IObserving;
25
import org.eclipse.core.databinding.observable.IStaleListener;
26
import org.eclipse.core.databinding.observable.ObservableTracker;
27
import org.eclipse.core.databinding.observable.StaleEvent;
28
import org.eclipse.core.databinding.observable.map.AbstractObservableMap;
29
import org.eclipse.core.databinding.observable.map.IMapChangeListener;
30
import org.eclipse.core.databinding.observable.map.IObservableMap;
31
import org.eclipse.core.databinding.observable.map.MapChangeEvent;
32
import org.eclipse.core.databinding.observable.map.MapDiff;
33
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
34
import org.eclipse.core.databinding.observable.value.IObservableValue;
35
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
36
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
37
import org.eclipse.core.internal.databinding.identity.IdentityMap;
38
import org.eclipse.core.internal.databinding.identity.IdentitySet;
39
import org.eclipse.core.internal.databinding.observable.Util;
40
41
/**
42
 * @since 1.4
43
 */
44
public class MapDetailValueObservableMap extends AbstractObservableMap
45
		implements IObserving {
46
47
	private IObservableMap masterMap;
48
49
	private IObservableFactory observableValueFactory;
50
51
	private Object detailValueType;
52
53
	private Set entrySet;
54
55
	private IdentityHashMap keyDetailMap = new IdentityHashMap();
56
57
	private IdentitySet staleDetailObservables = new IdentitySet();
58
59
	private IMapChangeListener masterMapListener = new IMapChangeListener() {
60
		public void handleMapChange(MapChangeEvent event) {
61
			handleMasterMapChange(event.diff);
62
		}
63
	};
64
65
	private IStaleListener masterStaleListener = new IStaleListener() {
66
		public void handleStale(StaleEvent staleEvent) {
67
			fireStale();
68
		}
69
	};
70
71
	private IStaleListener detailStaleListener = new IStaleListener() {
72
		public void handleStale(StaleEvent staleEvent) {
73
			addStaleDetailObservable((IObservableValue) staleEvent
74
					.getObservable());
75
		}
76
	};
77
78
	/**
79
	 * @param masterMap
80
	 * @param observableValueFactory
81
	 * @param detailValueType
82
	 */
83
	public MapDetailValueObservableMap(IObservableMap masterMap,
84
			IObservableFactory observableValueFactory, Object detailValueType) {
85
		super(masterMap.getRealm());
86
		this.masterMap = masterMap;
87
		this.observableValueFactory = observableValueFactory;
88
		this.detailValueType = detailValueType;
89
90
		// Add change/stale/dispose listeners on the master map.
91
		masterMap.addMapChangeListener(masterMapListener);
92
		masterMap.addStaleListener(masterStaleListener);
93
		masterMap.addDisposeListener(new IDisposeListener() {
94
			public void handleDispose(DisposeEvent event) {
95
				MapDetailValueObservableMap.this.dispose();
96
			}
97
		});
98
99
		// Initialize the map with the current state of the master map.
100
		MapDiff initMasterDiff = Diffs.computeMapDiff(Collections.EMPTY_MAP,
101
				masterMap);
102
		handleMasterMapChange(initMasterDiff);
103
	}
104
105
	private void handleMasterMapChange(MapDiff diff) {
106
		// Collect the detail values for the master values in the input diff.
107
		IdentityMap oldValues = new IdentityMap();
108
		IdentityMap newValues = new IdentityMap();
109
110
		// Handle added master values.
111
		Set addedKeys = diff.getAddedKeys();
112
		for (Iterator iter = addedKeys.iterator(); iter.hasNext();) {
113
			Object addedKey = iter.next();
114
115
			// For added master values, we set up a new detail observable.
116
			addDetailObservable(addedKey);
117
118
			// Get the value of the created detail observable for the new diff.
119
			IObservableValue detailValue = getDetailObservableValue(addedKey);
120
			newValues.put(addedKey, detailValue.getValue());
121
		}
122
123
		// Handle removed master values.
124
		Set removedKeys = diff.getRemovedKeys();
125
		for (Iterator iter = removedKeys.iterator(); iter.hasNext();) {
126
			Object removedKey = iter.next();
127
128
			// First of all, get the current detail value and add it to the set
129
			// of old values of the new diff.
130
			IObservableValue detailValue = getDetailObservableValue(removedKey);
131
			oldValues.put(removedKey, detailValue.getValue());
132
133
			// For removed master values, we dispose the detail observable.
134
			removeDetailObservable(removedKey);
135
		}
136
137
		// Handle changed master values.
138
		Set changedKeys = diff.getChangedKeys();
139
		for (Iterator iter = changedKeys.iterator(); iter.hasNext();) {
140
			Object changedKey = iter.next();
141
142
			// Get the detail value prior to the change and add it to the set of
143
			// old values of the new diff.
144
			IObservableValue oldDetailValue = getDetailObservableValue(changedKey);
145
			oldValues.put(changedKey, oldDetailValue.getValue());
146
147
			// Remove the old detail value for the old master value and add it
148
			// again for the new master value.
149
			removeDetailObservable(changedKey);
150
			addDetailObservable(changedKey);
151
152
			// Get the new detail value and add it to the set of new values.
153
			IObservableValue newDetailValue = getDetailObservableValue(changedKey);
154
			newValues.put(changedKey, newDetailValue.getValue());
155
		}
156
157
		// The different key sets are the same, only the values change.
158
		fireMapChange(Diffs.createMapDiff(addedKeys, removedKeys, changedKeys,
159
				oldValues, newValues));
160
	}
161
162
	private void addDetailObservable(final Object addedKey) {
163
		Object masterElement = masterMap.get(addedKey);
164
165
		IObservableValue detailValue = (IObservableValue) keyDetailMap
166
				.get(addedKey);
167
168
		if (detailValue == null) {
169
			detailValue = createDetailObservable(masterElement);
170
171
			keyDetailMap.put(addedKey, detailValue);
172
173
			detailValue.addValueChangeListener(new IValueChangeListener() {
174
				public void handleValueChange(ValueChangeEvent event) {
175
					if (!event.getObservableValue().isStale()) {
176
						staleDetailObservables.remove(event.getSource());
177
					}
178
179
					fireMapChange(Diffs.createMapDiffSingleChange(addedKey,
180
							event.diff.getOldValue(), event.diff.getNewValue()));
181
				}
182
			});
183
184
			if (detailValue.isStale()) {
185
				addStaleDetailObservable(detailValue);
186
			}
187
		}
188
189
		detailValue.addStaleListener(detailStaleListener);
190
	}
191
192
	private IObservableValue createDetailObservable(Object masterElement) {
193
		ObservableTracker.setIgnore(true);
194
		try {
195
			return (IObservableValue) observableValueFactory
196
					.createObservable(masterElement);
197
		} finally {
198
			ObservableTracker.setIgnore(false);
199
		}
200
	}
201
202
	private void removeDetailObservable(Object removedKey) {
203
		if (isDisposed()) {
204
			return;
205
		}
206
207
		IObservableValue detailValue = (IObservableValue) keyDetailMap
208
				.remove(removedKey);
209
		staleDetailObservables.remove(detailValue);
210
		detailValue.dispose();
211
	}
212
213
	private IObservableValue getDetailObservableValue(Object masterKey) {
214
		return (IObservableValue) keyDetailMap.get(masterKey);
215
	}
216
217
	private void addStaleDetailObservable(IObservableValue detailObservable) {
218
		boolean wasStale = isStale();
219
		staleDetailObservables.add(detailObservable);
220
		if (!wasStale) {
221
			fireStale();
222
		}
223
	}
224
225
	public Set keySet() {
226
		getterCalled();
227
228
		return masterMap.keySet();
229
	}
230
231
	public Object get(Object key) {
232
		getterCalled();
233
234
		if (!containsKey(key)) {
235
			return null;
236
		}
237
238
		IObservableValue detailValue = getDetailObservableValue(key);
239
		return detailValue.getValue();
240
	}
241
242
	public Object put(Object key, Object value) {
243
		if (!containsKey(key)) {
244
			return null;
245
		}
246
247
		IObservableValue detailValue = getDetailObservableValue(key);
248
		Object oldValue = detailValue.getValue();
249
		detailValue.setValue(value);
250
		return oldValue;
251
	}
252
253
	public boolean containsKey(Object key) {
254
		getterCalled();
255
256
		return masterMap.containsKey(key);
257
	}
258
259
	public Object remove(Object key) {
260
		checkRealm();
261
262
		if (!containsKey(key)) {
263
			return null;
264
		}
265
266
		IObservableValue detailValue = getDetailObservableValue(key);
267
		Object oldValue = detailValue.getValue();
268
269
		masterMap.remove(key);
270
271
		return oldValue;
272
	}
273
274
	public int size() {
275
		getterCalled();
276
277
		return masterMap.size();
278
	}
279
280
	public boolean isStale() {
281
		return super.isStale()
282
				|| (masterMap != null && masterMap.isStale())
283
				|| (staleDetailObservables != null && !staleDetailObservables
284
						.isEmpty());
285
	}
286
287
	public Object getKeyType() {
288
		return masterMap.getKeyType();
289
	}
290
291
	public Object getValueType() {
292
		return detailValueType;
293
	}
294
295
	public Object getObserved() {
296
		return masterMap;
297
	}
298
299
	public synchronized void dispose() {
300
		if (masterMap != null) {
301
			masterMap.removeMapChangeListener(masterMapListener);
302
			masterMap.removeStaleListener(masterStaleListener);
303
		}
304
305
		if (keyDetailMap != null) {
306
			for (Iterator iter = keyDetailMap.values().iterator(); iter
307
					.hasNext();) {
308
				IObservableValue detailValue = (IObservableValue) iter.next();
309
				detailValue.dispose();
310
			}
311
			keyDetailMap.clear();
312
		}
313
314
		masterMap = null;
315
		observableValueFactory = null;
316
		detailValueType = null;
317
		keyDetailMap = null;
318
		masterStaleListener = null;
319
		detailStaleListener = null;
320
		staleDetailObservables = null;
321
322
		super.dispose();
323
	}
324
325
	public Set entrySet() {
326
		getterCalled();
327
328
		if (entrySet == null) {
329
			entrySet = new EntrySet();
330
		}
331
		return entrySet;
332
	}
333
334
	private void getterCalled() {
335
		ObservableTracker.getterCalled(this);
336
	}
337
338
	private class EntrySet extends AbstractSet {
339
340
		public Iterator iterator() {
341
			final Iterator keyIterator = keySet().iterator();
342
			return new Iterator() {
343
344
				public boolean hasNext() {
345
					return keyIterator.hasNext();
346
				}
347
348
				public Object next() {
349
					Object key = keyIterator.next();
350
					return new MapEntry(key);
351
				}
352
353
				public void remove() {
354
					keyIterator.remove();
355
				}
356
			};
357
		}
358
359
		public int size() {
360
			return MapDetailValueObservableMap.this.size();
361
		}
362
	}
363
364
	private final class MapEntry implements Map.Entry {
365
366
		private final Object key;
367
368
		private MapEntry(Object key) {
369
			this.key = key;
370
		}
371
372
		public Object getKey() {
373
			MapDetailValueObservableMap.this.getterCalled();
374
			return key;
375
		}
376
377
		public Object getValue() {
378
			return MapDetailValueObservableMap.this.get(getKey());
379
		}
380
381
		public Object setValue(Object value) {
382
			return MapDetailValueObservableMap.this.put(getKey(), value);
383
		}
384
385
		public boolean equals(Object o) {
386
			MapDetailValueObservableMap.this.getterCalled();
387
			if (o == this)
388
				return true;
389
			if (o == null)
390
				return false;
391
			if (!(o instanceof Map.Entry))
392
				return false;
393
			Map.Entry that = (Map.Entry) o;
394
			return Util.equals(this.getKey(), that.getKey())
395
					&& Util.equals(this.getValue(), that.getValue());
396
		}
397
398
		public int hashCode() {
399
			MapDetailValueObservableMap.this.getterCalled();
400
			Object value = getValue();
401
			return (getKey() == null ? 0 : getKey().hashCode())
402
					^ (value == null ? 0 : value.hashCode());
403
		}
404
	}
405
}
(-)src/org/eclipse/core/internal/databinding/observable/masterdetail/SetDetailValueObservableMap.java (+178 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Ovidio Mallo and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Ovidio Mallo - initial API and implementation (bug 305367)
10
 ******************************************************************************/
11
12
package org.eclipse.core.internal.databinding.observable.masterdetail;
13
14
import java.util.HashMap;
15
import java.util.Map;
16
17
import org.eclipse.core.databinding.observable.IObserving;
18
import org.eclipse.core.databinding.observable.IStaleListener;
19
import org.eclipse.core.databinding.observable.ObservableTracker;
20
import org.eclipse.core.databinding.observable.StaleEvent;
21
import org.eclipse.core.databinding.observable.map.ComputedObservableMap;
22
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
23
import org.eclipse.core.databinding.observable.set.IObservableSet;
24
import org.eclipse.core.databinding.observable.value.IObservableValue;
25
import org.eclipse.core.databinding.observable.value.IValueChangeListener;
26
import org.eclipse.core.databinding.observable.value.ValueChangeEvent;
27
import org.eclipse.core.internal.databinding.identity.IdentitySet;
28
29
/**
30
 * @since 1.4
31
 */
32
public class SetDetailValueObservableMap extends ComputedObservableMap
33
		implements IObserving {
34
35
	private IObservableFactory observableValueFactory;
36
37
	private Map detailObservableValueMap = new HashMap();
38
39
	private IdentitySet staleDetailObservables = new IdentitySet();
40
41
	private IStaleListener detailStaleListener = new IStaleListener() {
42
		public void handleStale(StaleEvent staleEvent) {
43
			addStaleDetailObservable((IObservableValue) staleEvent
44
					.getObservable());
45
		}
46
	};
47
48
	/**
49
	 * @param masterKeySet
50
	 * @param observableValueFactory
51
	 * @param detailValueType
52
	 */
53
	public SetDetailValueObservableMap(IObservableSet masterKeySet,
54
			IObservableFactory observableValueFactory, Object detailValueType) {
55
		super(masterKeySet, detailValueType);
56
		this.observableValueFactory = observableValueFactory;
57
	}
58
59
	protected void hookListener(final Object addedKey) {
60
		final IObservableValue detailValue = getDetailObservableValue(addedKey);
61
62
		detailValue.addValueChangeListener(new IValueChangeListener() {
63
			public void handleValueChange(ValueChangeEvent event) {
64
				if (!event.getObservableValue().isStale()) {
65
					staleDetailObservables.remove(detailValue);
66
				}
67
68
				fireSingleChange(addedKey, event.diff.getOldValue(),
69
						event.diff.getNewValue());
70
			}
71
		});
72
73
		detailValue.addStaleListener(detailStaleListener);
74
	}
75
76
	protected void unhookListener(Object removedKey) {
77
		if (isDisposed()) {
78
			return;
79
		}
80
81
		IObservableValue detailValue = (IObservableValue) detailObservableValueMap
82
				.remove(removedKey);
83
		staleDetailObservables.remove(detailValue);
84
		detailValue.dispose();
85
	}
86
87
	private IObservableValue getDetailObservableValue(Object masterKey) {
88
		IObservableValue detailValue = (IObservableValue) detailObservableValueMap
89
				.get(masterKey);
90
91
		if (detailValue == null) {
92
			ObservableTracker.setIgnore(true);
93
			try {
94
				detailValue = (IObservableValue) observableValueFactory
95
						.createObservable(masterKey);
96
			} finally {
97
				ObservableTracker.setIgnore(false);
98
			}
99
100
			detailObservableValueMap.put(masterKey, detailValue);
101
102
			if (detailValue.isStale()) {
103
				addStaleDetailObservable(detailValue);
104
			}
105
		}
106
107
		return detailValue;
108
	}
109
110
	private void addStaleDetailObservable(IObservableValue detailObservable) {
111
		boolean wasStale = isStale();
112
		staleDetailObservables.add(detailObservable);
113
		if (!wasStale) {
114
			fireStale();
115
		}
116
	}
117
118
	protected Object doGet(Object key) {
119
		IObservableValue detailValue = getDetailObservableValue(key);
120
		return detailValue.getValue();
121
	}
122
123
	protected Object doPut(Object key, Object value) {
124
		IObservableValue detailValue = getDetailObservableValue(key);
125
		Object oldValue = detailValue.getValue();
126
		detailValue.setValue(value);
127
		return oldValue;
128
	}
129
130
	public boolean containsKey(Object key) {
131
		getterCalled();
132
133
		return keySet().contains(key);
134
	}
135
136
	public Object remove(Object key) {
137
		checkRealm();
138
139
		if (!containsKey(key)) {
140
			return null;
141
		}
142
143
		IObservableValue detailValue = getDetailObservableValue(key);
144
		Object oldValue = detailValue.getValue();
145
146
		keySet().remove(key);
147
148
		return oldValue;
149
	}
150
151
	public int size() {
152
		getterCalled();
153
154
		return keySet().size();
155
	}
156
157
	public boolean isStale() {
158
		return super.isStale() || staleDetailObservables != null
159
				&& !staleDetailObservables.isEmpty();
160
	}
161
162
	public Object getObserved() {
163
		return keySet();
164
	}
165
166
	public synchronized void dispose() {
167
		super.dispose();
168
169
		observableValueFactory = null;
170
		detailObservableValueMap = null;
171
		detailStaleListener = null;
172
		staleDetailObservables = null;
173
	}
174
175
	private void getterCalled() {
176
		ObservableTracker.getterCalled(this);
177
	}
178
}
(-)META-INF/MANIFEST.MF (-1 / +1 lines)
Lines 2-8 Link Here
2
Bundle-ManifestVersion: 2
2
Bundle-ManifestVersion: 2
3
Bundle-Name: %pluginName
3
Bundle-Name: %pluginName
4
Bundle-SymbolicName: org.eclipse.core.databinding.property
4
Bundle-SymbolicName: org.eclipse.core.databinding.property
5
Bundle-Version: 1.3.0.qualifier
5
Bundle-Version: 1.4.0.qualifier
6
Bundle-ClassPath: .
6
Bundle-ClassPath: .
7
Bundle-Vendor: %providerName
7
Bundle-Vendor: %providerName
8
Bundle-Localization: plugin
8
Bundle-Localization: plugin
(-)src/org/eclipse/core/databinding/property/value/ValueProperty.java (-2 / +30 lines)
Lines 8-21 Link Here
8
 * Contributors:
8
 * Contributors:
9
 *     Matthew Hall - initial API and implementation (bug 194734)
9
 *     Matthew Hall - initial API and implementation (bug 194734)
10
 *     Matthew Hall - bug 195222
10
 *     Matthew Hall - bug 195222
11
 *     Ovidio Mallo - bug 305367
11
 ******************************************************************************/
12
 ******************************************************************************/
12
13
13
package org.eclipse.core.databinding.property.value;
14
package org.eclipse.core.databinding.property.value;
14
15
15
import org.eclipse.core.databinding.observable.IObservable;
16
import org.eclipse.core.databinding.observable.IObservable;
16
import org.eclipse.core.databinding.observable.Realm;
17
import org.eclipse.core.databinding.observable.Realm;
18
import org.eclipse.core.databinding.observable.list.IObservableList;
19
import org.eclipse.core.databinding.observable.map.IObservableMap;
17
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
20
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
18
import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
21
import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
22
import org.eclipse.core.databinding.observable.set.IObservableSet;
19
import org.eclipse.core.databinding.observable.value.IObservableValue;
23
import org.eclipse.core.databinding.observable.value.IObservableValue;
20
import org.eclipse.core.databinding.property.list.IListProperty;
24
import org.eclipse.core.databinding.property.list.IListProperty;
21
import org.eclipse.core.databinding.property.map.IMapProperty;
25
import org.eclipse.core.databinding.property.map.IMapProperty;
Lines 108-115 Link Here
108
	}
112
	}
109
113
110
	public IObservableValue observeDetail(IObservableValue master) {
114
	public IObservableValue observeDetail(IObservableValue master) {
111
		return MasterDetailObservables.detailValue(master, valueFactory(master
115
		return MasterDetailObservables.detailValue(master,
112
				.getRealm()), getValueType());
116
				valueFactory(master.getRealm()), getValueType());
117
	}
118
119
	/**
120
	 * @since 1.4
121
	 */
122
	public IObservableList observeDetail(IObservableList master) {
123
		return MasterDetailObservables.detailValues(master,
124
				valueFactory(master.getRealm()), getValueType());
125
	}
126
127
	/**
128
	 * @since 1.4
129
	 */
130
	public IObservableMap observeDetail(IObservableSet master) {
131
		return MasterDetailObservables.detailValues(master,
132
				valueFactory(master.getRealm()), getValueType());
133
	}
134
135
	/**
136
	 * @since 1.4
137
	 */
138
	public IObservableMap observeDetail(IObservableMap master) {
139
		return MasterDetailObservables.detailValues(master,
140
				valueFactory(master.getRealm()), getValueType());
113
	}
141
	}
114
142
115
	public final IValueProperty value(IValueProperty detailValue) {
143
	public final IValueProperty value(IValueProperty detailValue) {
(-)src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.java (+391 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Ovidio Mallo and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Ovidio Mallo - initial API and implementation (bug 305367)
10
 ******************************************************************************/
11
12
package org.eclipse.core.tests.internal.databinding.observable.masterdetail;
13
14
import java.util.ArrayList;
15
import java.util.Collections;
16
import java.util.List;
17
18
import junit.framework.Test;
19
import junit.framework.TestSuite;
20
21
import org.eclipse.core.databinding.beans.BeansObservables;
22
import org.eclipse.core.databinding.observable.IObservable;
23
import org.eclipse.core.databinding.observable.IObservableCollection;
24
import org.eclipse.core.databinding.observable.Realm;
25
import org.eclipse.core.databinding.observable.list.IObservableList;
26
import org.eclipse.core.databinding.observable.list.ListDiff;
27
import org.eclipse.core.databinding.observable.list.ListDiffEntry;
28
import org.eclipse.core.databinding.observable.list.WritableList;
29
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
30
import org.eclipse.core.databinding.observable.value.WritableValue;
31
import org.eclipse.core.internal.databinding.observable.masterdetail.ListDetailValueObservableList;
32
import org.eclipse.jface.databinding.conformance.ObservableListContractTest;
33
import org.eclipse.jface.databinding.conformance.delegate.AbstractObservableCollectionContractDelegate;
34
import org.eclipse.jface.databinding.conformance.util.ListChangeEventTracker;
35
import org.eclipse.jface.examples.databinding.model.SimplePerson;
36
import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
37
38
/**
39
 * @since 1.3
40
 */
41
public class ListDetailValueObservableListTest extends
42
		AbstractDefaultRealmTestCase {
43
44
	public static Test suite() {
45
		TestSuite suite = new TestSuite(
46
				ListDetailValueObservableListTest.class.getName());
47
		suite.addTestSuite(ListDetailValueObservableListTest.class);
48
		suite.addTest(ObservableListContractTest.suite(new Delegate()));
49
		return suite;
50
	}
51
52
	public void testUnmodifiability() {
53
		WritableList masterObservableList = new WritableList();
54
		masterObservableList.add(new SimplePerson());
55
		masterObservableList.add(new SimplePerson());
56
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
57
				masterObservableList, BeansObservables.valueFactory("name"),
58
				null);
59
60
		try {
61
			ldol.add("name");
62
			fail("ListDetailValueObservableList must not be modifiable.");
63
		} catch (UnsupportedOperationException e) {
64
			// expected exception
65
		}
66
67
		try {
68
			ldol.remove(masterObservableList.get(0));
69
			fail("ListDetailValueObservableList must not be modifiable.");
70
		} catch (UnsupportedOperationException e) {
71
			// expected exception
72
		}
73
74
		try {
75
			ldol.removeAll(Collections.singleton(masterObservableList.get(0)));
76
			fail("ListDetailValueObservableList must not be modifiable.");
77
		} catch (UnsupportedOperationException e) {
78
			// expected exception
79
		}
80
81
		try {
82
			ldol.retainAll(Collections.EMPTY_LIST);
83
			fail("ListDetailValueObservableList must not be modifiable.");
84
		} catch (UnsupportedOperationException e) {
85
			// expected exception
86
		}
87
88
		try {
89
			ldol.move(0, 1);
90
			fail("ListDetailValueObservableList must not be modifiable.");
91
		} catch (UnsupportedOperationException e) {
92
			// expected exception
93
		}
94
	}
95
96
	public void testGetElementType() {
97
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
98
				new WritableList(), BeansObservables.valueFactory("name"),
99
				String.class);
100
101
		assertSame(String.class, ldol.getElementType());
102
	}
103
104
	public void testGetObserved() {
105
		WritableList masterList = new WritableList();
106
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
107
				masterList, BeansObservables.valueFactory("name"), String.class);
108
109
		// The observed object is the master list.
110
		assertSame(masterList, ldol.getObserved());
111
	}
112
113
	public void testMasterListInitiallyNotEmpty() {
114
		WritableList masterList = new WritableList();
115
		SimplePerson person = new SimplePerson();
116
		person.setName("name");
117
		masterList.add(person);
118
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
119
				masterList, BeansObservables.valueFactory("name"), String.class);
120
121
		// Make sure that a non-empty master list is initialized correctly.
122
		assertEquals(masterList.size(), ldol.size());
123
		assertEquals(person.getName(), ldol.get(0));
124
	}
125
126
	public void testAddRemove() {
127
		WritableList masterList = new WritableList();
128
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
129
				masterList, BeansObservables.valueFactory("name"), String.class);
130
131
		// Initially, the detail list is empty.
132
		assertTrue(ldol.isEmpty());
133
134
		// Add a first person and check that its name is in the detail list.
135
		SimplePerson p1 = new SimplePerson();
136
		p1.setName("name1");
137
		masterList.add(p1);
138
		assertEquals(masterList.size(), ldol.size());
139
		assertEquals(p1.getName(), ldol.get(0));
140
141
		// Add a second person and check that it's name is in the detail list.
142
		SimplePerson p2 = new SimplePerson();
143
		p2.setName("name2");
144
		masterList.add(p2);
145
		assertEquals(masterList.size(), ldol.size());
146
		assertEquals(p2.getName(), ldol.get(1));
147
148
		// Remove the first person from the master list and check that we still
149
		// have the name of the second person in the detail list.
150
		masterList.remove(0);
151
		assertEquals(masterList.size(), ldol.size());
152
		assertEquals(p2.getName(), ldol.get(0));
153
154
		// Remove the second person as well.
155
		masterList.remove(0);
156
		assertTrue(ldol.isEmpty());
157
	}
158
159
	public void testChangeDetail() {
160
		WritableList masterList = new WritableList();
161
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
162
				masterList, BeansObservables.valueFactory("name"), String.class);
163
164
		// Change the detail attribute explicitly.
165
		SimplePerson p1 = new SimplePerson();
166
		p1.setName("name1");
167
		masterList.add(p1);
168
		assertEquals(p1.getName(), ldol.get(0));
169
		p1.setName("name2");
170
		assertEquals(p1.getName(), ldol.get(0));
171
172
		// Change the detail attribute by changing the master.
173
		SimplePerson p2 = new SimplePerson();
174
		p2.setName("name3");
175
		masterList.set(0, p2);
176
		assertEquals(p2.getName(), ldol.get(0));
177
	}
178
179
	public void testSet() {
180
		WritableList masterList = new WritableList();
181
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
182
				masterList, BeansObservables.valueFactory("name"), String.class);
183
184
		// Change the detail attribute explicitly.
185
		SimplePerson person = new SimplePerson();
186
		person.setName("name1");
187
		masterList.add(person);
188
		assertEquals(person.getName(), ldol.get(0));
189
190
		// Set a new name on the detail list.
191
		ldol.set(0, "name2");
192
		// Check that the name has been propagated to the master.
193
		assertEquals("name2", person.getName());
194
		assertEquals(person.getName(), ldol.get(0));
195
	}
196
197
	public void testDuplicateMasterElements() {
198
		WritableList masterList = new WritableList();
199
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
200
				masterList, BeansObservables.valueFactory("name"), String.class);
201
202
		SimplePerson master = new SimplePerson();
203
		master.setName("name1");
204
205
		// Add the same master twice.
206
		masterList.add(master);
207
		masterList.add(master);
208
209
		// Attach the change listener to the detail list.
210
		ListChangeEventTracker changeTracker = ListChangeEventTracker
211
				.observe(ldol);
212
213
		// Setting the name on master should trigger an event on both
214
		// occurrences of in the master list.
215
		master.setName("name2");
216
217
		// We should have 2 replace diffs, i.e. 4 diff entries.
218
		assertEquals(1, changeTracker.count);
219
		assertEquals(4, changeTracker.event.diff.getDifferences().length);
220
		assertReplaceDiffAt(changeTracker.event.diff, 0, 0, "name1", "name2");
221
		assertReplaceDiffAt(changeTracker.event.diff, 2, 0, "name1", "name2");
222
223
		// Remove one instance of the master (one will remain).
224
		masterList.remove(master);
225
226
		// It should still be possible to work on the remaining master instance.
227
		ldol.set(0, "name3");
228
		assertEquals("name3", master.getName());
229
	}
230
231
	public void testDetailObservableChangeEvent() {
232
		WritableList masterList = new WritableList();
233
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
234
				masterList, BeansObservables.valueFactory("name"), String.class);
235
236
		ListChangeEventTracker changeTracker = ListChangeEventTracker
237
				.observe(ldol);
238
239
		SimplePerson person = new SimplePerson();
240
		person.setName("old name");
241
242
		// Initially, we should not have received any event.
243
		assertEquals(0, changeTracker.count);
244
245
		// Add the person and check that we receive an addition event on the
246
		// correct index and with the correct value.
247
		masterList.add(person);
248
		assertEquals(1, changeTracker.count);
249
		assertEquals(1, changeTracker.event.diff.getDifferences().length);
250
		assertTrue(changeTracker.event.diff.getDifferences()[0].isAddition());
251
		assertEquals(0,
252
				changeTracker.event.diff.getDifferences()[0].getPosition());
253
		assertEquals(person.getName(),
254
				changeTracker.event.diff.getDifferences()[0].getElement());
255
256
		// Change the detail property and check that we receive a replace event.
257
		person.setName("new name");
258
		assertEquals(2, changeTracker.count);
259
		assertIsSingleReplaceDiff(changeTracker.event.diff, 0, "old name",
260
				"new name");
261
	}
262
263
	private void assertIsSingleReplaceDiff(ListDiff diff, int index,
264
			Object oldElement, Object newElement) {
265
		// We should have 2 diff entries.
266
		assertEquals(2, diff.getDifferences().length);
267
268
		// Check that it indeed is a replace diff.
269
		assertReplaceDiffAt(diff, 0, index, oldElement, newElement);
270
	}
271
272
	private void assertReplaceDiffAt(ListDiff diff, int diffOffset, int index,
273
			Object oldElement, Object newElement) {
274
		ListDiffEntry entry1 = diff.getDifferences()[0];
275
		ListDiffEntry entry2 = diff.getDifferences()[1];
276
277
		// One diff entry must be an addition, the other a removal.
278
		assertTrue(entry1.isAddition() != entry2.isAddition());
279
280
		// Check for the index on the diff entries.
281
		assertEquals(index, entry1.getPosition());
282
		assertEquals(index, entry2.getPosition());
283
284
		// Check for the old/new element values on both diff entries.
285
		if (entry1.isAddition()) {
286
			assertEquals(oldElement, entry2.getElement());
287
			assertEquals(newElement, entry1.getElement());
288
		} else {
289
			assertEquals(oldElement, entry1.getElement());
290
			assertEquals(newElement, entry2.getElement());
291
		}
292
	}
293
294
	public void testMasterNull() {
295
		WritableList masterObservableList = new WritableList();
296
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
297
				masterObservableList, BeansObservables.valueFactory("name"),
298
				String.class);
299
300
		// Make sure null values are handled gracefully.
301
		masterObservableList.add(null);
302
		assertEquals(1, ldol.size());
303
		assertNull(ldol.get(0));
304
	}
305
306
	public void testDetailObservableValuesAreDisposed() {
307
		final List detailObservables = new ArrayList();
308
		IObservableFactory detailValueFactory = new IObservableFactory() {
309
			public IObservable createObservable(Object target) {
310
				WritableValue detailObservable = new WritableValue();
311
				// Remember the created observables.
312
				detailObservables.add(detailObservable);
313
				return detailObservable;
314
			}
315
		};
316
317
		WritableList masterList = new WritableList();
318
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
319
				masterList, detailValueFactory, null);
320
321
		masterList.add(new Object());
322
		masterList.add(new Object());
323
324
		assertEquals(ldol.size(), detailObservables.size());
325
326
		// No detail observables should be disposed yet.
327
		assertFalse(((WritableValue) detailObservables.get(0)).isDisposed());
328
		assertFalse(((WritableValue) detailObservables.get(1)).isDisposed());
329
330
		// Only the detail observable for the removed master should be disposed.
331
		masterList.remove(1);
332
		assertFalse(((WritableValue) detailObservables.get(0)).isDisposed());
333
		assertTrue(((WritableValue) detailObservables.get(1)).isDisposed());
334
335
		// After disposing the detail list, all detail observables should be
336
		// disposed.
337
		ldol.dispose();
338
		assertTrue(((WritableValue) detailObservables.get(0)).isDisposed());
339
		assertTrue(((WritableValue) detailObservables.get(1)).isDisposed());
340
	}
341
342
	public void testDisposeOnMasterDisposed() {
343
		WritableList masterList = new WritableList();
344
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
345
				masterList, BeansObservables.valueFactory("name"), String.class);
346
347
		// Initially, nothing should be disposed.
348
		assertFalse(masterList.isDisposed());
349
		assertFalse(ldol.isDisposed());
350
351
		// Upon disposing the master list, the detail list should be disposed as
352
		// well.
353
		masterList.dispose();
354
		assertTrue(masterList.isDisposed());
355
		assertTrue(ldol.isDisposed());
356
	}
357
358
	private static class Delegate extends
359
			AbstractObservableCollectionContractDelegate {
360
		public IObservableCollection createObservableCollection(Realm realm,
361
				int elementCount) {
362
			WritableList masterList = new WritableList(realm);
363
			for (int i = 0; i < elementCount; i++) {
364
				masterList.add(new SimplePerson());
365
			}
366
367
			return new TestListDetailValueObservableList(masterList,
368
					BeansObservables.valueFactory(realm, "name"), String.class);
369
		}
370
371
		public void change(IObservable observable) {
372
			TestListDetailValueObservableList ldol = (TestListDetailValueObservableList) observable;
373
			ldol.masterList.add(new SimplePerson());
374
		}
375
376
		public Object getElementType(IObservableCollection collection) {
377
			return String.class;
378
		}
379
	}
380
381
	private static class TestListDetailValueObservableList extends
382
			ListDetailValueObservableList {
383
		final IObservableList masterList;
384
385
		public TestListDetailValueObservableList(IObservableList masterList,
386
				IObservableFactory detailValueFactory, Object detailType) {
387
			super(masterList, detailValueFactory, detailType);
388
			this.masterList = masterList;
389
		}
390
	}
391
}
(-)src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/MapDetailValueObservableMapTest.java (+309 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Ovidio Mallo and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Ovidio Mallo - initial API and implementation (bug 305367)
10
 ******************************************************************************/
11
12
package org.eclipse.core.tests.internal.databinding.observable.masterdetail;
13
14
import java.util.HashMap;
15
import java.util.Map;
16
17
import junit.framework.Test;
18
import junit.framework.TestSuite;
19
20
import org.eclipse.core.databinding.beans.BeansObservables;
21
import org.eclipse.core.databinding.observable.IObservable;
22
import org.eclipse.core.databinding.observable.map.WritableMap;
23
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
24
import org.eclipse.core.databinding.observable.value.WritableValue;
25
import org.eclipse.core.internal.databinding.observable.masterdetail.MapDetailValueObservableMap;
26
import org.eclipse.jface.databinding.conformance.util.MapChangeEventTracker;
27
import org.eclipse.jface.examples.databinding.model.SimplePerson;
28
import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
29
30
/**
31
 * @since 1.3
32
 */
33
public class MapDetailValueObservableMapTest extends
34
		AbstractDefaultRealmTestCase {
35
36
	public static Test suite() {
37
		TestSuite suite = new TestSuite(
38
				MapDetailValueObservableMapTest.class.getName());
39
		suite.addTestSuite(MapDetailValueObservableMapTest.class);
40
		return suite;
41
	}
42
43
	public void testGetKeyType() {
44
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
45
				new WritableMap(SimplePerson.class, SimplePerson.class),
46
				BeansObservables.valueFactory("name"), String.class);
47
48
		assertSame(SimplePerson.class, mdom.getKeyType());
49
	}
50
51
	public void testGetValueType() {
52
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
53
				new WritableMap(), BeansObservables.valueFactory("name"),
54
				String.class);
55
56
		assertSame(String.class, mdom.getValueType());
57
	}
58
59
	public void testGetObserved() {
60
		WritableMap masterMap = new WritableMap();
61
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
62
				masterMap, BeansObservables.valueFactory("name"), String.class);
63
64
		// The observed object is the master key set.
65
		assertSame(masterMap, mdom.getObserved());
66
	}
67
68
	public void testMasterSetInitiallyNotEmpty() {
69
		WritableMap masterMap = new WritableMap();
70
		SimplePerson person = new SimplePerson();
71
		person.setName("name");
72
		masterMap.put(person, person);
73
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
74
				masterMap, BeansObservables.valueFactory("name"), String.class);
75
76
		// Make sure that a non-empty master key set is initialized correctly.
77
		assertEquals(masterMap.size(), mdom.size());
78
		assertEquals(person.getName(), mdom.get(person));
79
	}
80
81
	public void testAddRemove() {
82
		WritableMap masterMap = new WritableMap();
83
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
84
				masterMap, BeansObservables.valueFactory("name"), String.class);
85
86
		// Initially, the detail map is empty.
87
		assertTrue(mdom.isEmpty());
88
89
		// Add a first person and check that its name is in the detail map.
90
		SimplePerson p1 = new SimplePerson();
91
		p1.setName("name1");
92
		masterMap.put(p1, p1);
93
		assertEquals(masterMap.size(), mdom.size());
94
		assertEquals(p1.getName(), mdom.get(p1));
95
96
		// Add a second person and check that it's name is in the detail map.
97
		SimplePerson p2 = new SimplePerson();
98
		p2.setName("name2");
99
		masterMap.put(p2, p2);
100
		assertEquals(masterMap.size(), mdom.size());
101
		assertEquals(p2.getName(), mdom.get(p2));
102
103
		// Remove the first person from the master map and check that we still
104
		// have the name of the second person in the detail map.
105
		masterMap.remove(p1);
106
		assertEquals(masterMap.size(), mdom.size());
107
		assertEquals(p2.getName(), mdom.get(p2));
108
109
		// Remove the second person as well.
110
		masterMap.remove(p2);
111
		assertTrue(mdom.isEmpty());
112
	}
113
114
	public void testChangeDetail() {
115
		WritableMap masterMap = new WritableMap();
116
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
117
				masterMap, BeansObservables.valueFactory("name"), String.class);
118
119
		// Change the detail attribute explicitly.
120
		SimplePerson p1 = new SimplePerson();
121
		p1.setName("name1");
122
		masterMap.put(p1, p1);
123
		assertEquals(p1.getName(), mdom.get(p1));
124
		p1.setName("name2");
125
		assertEquals(p1.getName(), mdom.get(p1));
126
127
		// Change the detail attribute by changing the master.
128
		SimplePerson p2 = new SimplePerson();
129
		p2.setName("name3");
130
		masterMap.put(p1, p2);
131
		assertEquals(p2.getName(), mdom.get(p1));
132
	}
133
134
	public void testPut() {
135
		WritableMap masterMap = new WritableMap();
136
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
137
				masterMap, BeansObservables.valueFactory("name"), String.class);
138
139
		// Change the detail attribute explicitly.
140
		SimplePerson person = new SimplePerson();
141
		person.setName("name1");
142
		masterMap.put(person, person);
143
		assertEquals(person.getName(), mdom.get(person));
144
145
		// Set a new name on the detail map.
146
		mdom.put(person, "name2");
147
		// Check that the name has been propagated to the master.
148
		assertEquals("name2", person.getName());
149
		assertEquals(person.getName(), mdom.get(person));
150
	}
151
152
	public void testContainsValue() {
153
		WritableMap masterMap = new WritableMap();
154
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
155
				masterMap, BeansObservables.valueFactory("name"), String.class);
156
157
		// Add a person with a given name.
158
		SimplePerson person = new SimplePerson();
159
		person.setName("name");
160
		masterMap.put(person, person);
161
162
		// Make sure the name of the person is contained.
163
		assertTrue(mdom.containsValue(person.getName()));
164
165
		// Remove the person and make sure that it's name cannot be found
166
		// anymore.
167
		masterMap.remove(person);
168
		assertFalse(mdom.containsValue(person.getName()));
169
	}
170
171
	public void testRemove() {
172
		WritableMap masterMap = new WritableMap();
173
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
174
				masterMap, BeansObservables.valueFactory("name"), String.class);
175
176
		// Add two person objects to the map.
177
		SimplePerson p1 = new SimplePerson();
178
		SimplePerson p2 = new SimplePerson();
179
		masterMap.put(p1, p1);
180
		masterMap.put(p2, p2);
181
182
		// Initially, both person objects should be contained in the detail map.
183
		assertTrue(mdom.containsKey(p1));
184
		assertTrue(mdom.containsKey(p2));
185
186
		// Remove one person and check that it is not contained anymore.
187
		mdom.remove(p1);
188
		assertFalse(mdom.containsKey(p1));
189
		assertTrue(mdom.containsKey(p2));
190
191
		// Trying to remove a non-existent is allowed but has no effect.
192
		mdom.remove(p1);
193
		assertFalse(mdom.containsKey(p1));
194
		assertTrue(mdom.containsKey(p2));
195
	}
196
197
	public void testDetailObservableChangeEvent() {
198
		WritableMap masterMap = new WritableMap();
199
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
200
				masterMap, BeansObservables.valueFactory("name"), String.class);
201
202
		MapChangeEventTracker changeTracker = MapChangeEventTracker
203
				.observe(mdom);
204
205
		SimplePerson person = new SimplePerson();
206
		person.setName("old name");
207
208
		// Initially, we should not have received any event.
209
		assertEquals(0, changeTracker.count);
210
211
		// Add the person and check that we receive an addition event on the
212
		// correct index and with the correct value.
213
		masterMap.put(person, person);
214
		assertEquals(1, changeTracker.count);
215
		assertEquals(1, changeTracker.event.diff.getAddedKeys().size());
216
		assertEquals(0, changeTracker.event.diff.getRemovedKeys().size());
217
		assertEquals(0, changeTracker.event.diff.getChangedKeys().size());
218
		assertSame(person, changeTracker.event.diff.getAddedKeys().iterator()
219
				.next());
220
		assertNull(changeTracker.event.diff.getOldValue(person));
221
		assertEquals("old name", changeTracker.event.diff.getNewValue(person));
222
223
		// Change the detail property and check that we receive a replace
224
		person.setName("new name");
225
		assertEquals(2, changeTracker.count);
226
		assertEquals(0, changeTracker.event.diff.getAddedKeys().size());
227
		assertEquals(0, changeTracker.event.diff.getRemovedKeys().size());
228
		assertEquals(1, changeTracker.event.diff.getChangedKeys().size());
229
		assertSame(person, changeTracker.event.diff.getChangedKeys().iterator()
230
				.next());
231
		assertEquals("old name", changeTracker.event.diff.getOldValue(person));
232
		assertEquals("new name", changeTracker.event.diff.getNewValue(person));
233
	}
234
235
	public void testMasterNull() {
236
		WritableMap masterMap = new WritableMap();
237
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
238
				masterMap, BeansObservables.valueFactory("name"), String.class);
239
240
		// Make sure null values are handled gracefully.
241
		masterMap.put(null, null);
242
		assertEquals(1, mdom.size());
243
		assertNull(mdom.get(null));
244
	}
245
246
	public void testDetailObservableValuesAreDisposed() {
247
		final Map detailObservables = new HashMap();
248
		IObservableFactory detailValueFactory = new IObservableFactory() {
249
			public IObservable createObservable(Object target) {
250
				WritableValue detailObservable = new WritableValue();
251
				// Remember the created observables.
252
				detailObservables.put(target, detailObservable);
253
				return detailObservable;
254
			}
255
		};
256
257
		WritableMap masterMap = new WritableMap();
258
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
259
				masterMap, detailValueFactory, null);
260
261
		Object master1 = new Object();
262
		Object master2 = new Object();
263
		masterMap.put(master1, master1);
264
		masterMap.put(master2, master2);
265
266
		// Attach a listener in order to ensure that all detail observables are
267
		// actually created.
268
		MapChangeEventTracker.observe(mdom);
269
270
		assertEquals(mdom.size(), detailObservables.size());
271
272
		// No detail observables should be disposed yet.
273
		assertFalse(((WritableValue) detailObservables.get(master1))
274
				.isDisposed());
275
		assertFalse(((WritableValue) detailObservables.get(master2))
276
				.isDisposed());
277
278
		// Only the detail observable for the removed master should be disposed.
279
		masterMap.remove(master2);
280
		assertFalse(((WritableValue) detailObservables.get(master1))
281
				.isDisposed());
282
		assertTrue(((WritableValue) detailObservables.get(master2))
283
				.isDisposed());
284
285
		// After disposing the detail map, all detail observables should be
286
		// disposed.
287
		mdom.dispose();
288
		assertTrue(((WritableValue) detailObservables.get(master1))
289
				.isDisposed());
290
		assertTrue(((WritableValue) detailObservables.get(master2))
291
				.isDisposed());
292
	}
293
294
	public void testDisposeOnMasterDisposed() {
295
		WritableMap masterMap = new WritableMap();
296
		MapDetailValueObservableMap mdom = new MapDetailValueObservableMap(
297
				masterMap, BeansObservables.valueFactory("name"), String.class);
298
299
		// Initially, nothing should be disposed.
300
		assertFalse(masterMap.isDisposed());
301
		assertFalse(mdom.isDisposed());
302
303
		// Upon disposing the master map, the detail map should be disposed as
304
		// well.
305
		masterMap.dispose();
306
		assertTrue(masterMap.isDisposed());
307
		assertTrue(mdom.isDisposed());
308
	}
309
}
(-)src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/SetDetailValueObservableMapTest.java (+311 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Ovidio Mallo and others.
3
 * All rights reserved. This program and the accompanying materials
4
 * are made available under the terms of the Eclipse Public License v1.0
5
 * which accompanies this distribution, and is available at
6
 * http://www.eclipse.org/legal/epl-v10.html
7
 *
8
 * Contributors:
9
 *     Ovidio Mallo - initial API and implementation (bug 305367)
10
 ******************************************************************************/
11
12
package org.eclipse.core.tests.internal.databinding.observable.masterdetail;
13
14
import java.util.HashMap;
15
import java.util.Map;
16
17
import junit.framework.Test;
18
import junit.framework.TestSuite;
19
20
import org.eclipse.core.databinding.beans.BeansObservables;
21
import org.eclipse.core.databinding.observable.IObservable;
22
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
23
import org.eclipse.core.databinding.observable.set.WritableSet;
24
import org.eclipse.core.databinding.observable.value.WritableValue;
25
import org.eclipse.core.internal.databinding.observable.masterdetail.SetDetailValueObservableMap;
26
import org.eclipse.jface.databinding.conformance.util.MapChangeEventTracker;
27
import org.eclipse.jface.examples.databinding.model.SimplePerson;
28
import org.eclipse.jface.tests.databinding.AbstractDefaultRealmTestCase;
29
30
/**
31
 * @since 1.3
32
 */
33
public class SetDetailValueObservableMapTest extends
34
		AbstractDefaultRealmTestCase {
35
36
	public static Test suite() {
37
		TestSuite suite = new TestSuite(SetDetailValueObservableMapTest.class
38
				.getName());
39
		suite.addTestSuite(SetDetailValueObservableMapTest.class);
40
		return suite;
41
	}
42
43
	public void testGetValueType() {
44
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
45
				new WritableSet(), BeansObservables.valueFactory("name"),
46
				String.class);
47
48
		assertSame(String.class, sdom.getValueType());
49
	}
50
51
	public void testGetObserved() {
52
		WritableSet masterKeySet = new WritableSet();
53
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
54
				masterKeySet, BeansObservables.valueFactory("name"),
55
				String.class);
56
57
		// The observed object is the master key set.
58
		assertSame(masterKeySet, sdom.getObserved());
59
	}
60
61
	public void testMasterSetInitiallyNotEmpty() {
62
		WritableSet masterKeySet = new WritableSet();
63
		SimplePerson person = new SimplePerson();
64
		person.setName("name");
65
		masterKeySet.add(person);
66
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
67
				masterKeySet, BeansObservables.valueFactory("name"),
68
				String.class);
69
70
		// Make sure that a non-empty master key set is initialized correctly.
71
		assertEquals(masterKeySet.size(), sdom.size());
72
		assertEquals(person.getName(), sdom.get(person));
73
	}
74
75
	public void testAddRemove() {
76
		WritableSet masterKeySet = new WritableSet();
77
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
78
				masterKeySet, BeansObservables.valueFactory("name"),
79
				String.class);
80
81
		// Initially, the detail map is empty.
82
		assertTrue(sdom.isEmpty());
83
84
		// Add a first person and check that its name is in the detail list.
85
		SimplePerson p1 = new SimplePerson();
86
		p1.setName("name1");
87
		masterKeySet.add(p1);
88
		assertEquals(masterKeySet.size(), sdom.size());
89
		assertEquals(p1.getName(), sdom.get(p1));
90
91
		// Add a second person and check that it's name is in the detail list.
92
		SimplePerson p2 = new SimplePerson();
93
		p2.setName("name2");
94
		masterKeySet.add(p2);
95
		assertEquals(masterKeySet.size(), sdom.size());
96
		assertEquals(p2.getName(), sdom.get(p2));
97
98
		// Remove the first person from the master list and check that we still
99
		// have the name of the second person in the detail list.
100
		masterKeySet.remove(p1);
101
		assertEquals(masterKeySet.size(), sdom.size());
102
		assertEquals(p2.getName(), sdom.get(p2));
103
104
		// Remove the second person as well.
105
		masterKeySet.remove(p2);
106
		assertTrue(sdom.isEmpty());
107
	}
108
109
	public void testChangeDetail() {
110
		WritableSet masterKeySet = new WritableSet();
111
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
112
				masterKeySet, BeansObservables.valueFactory("name"),
113
				String.class);
114
115
		// Change the detail attribute explicitly.
116
		SimplePerson p1 = new SimplePerson();
117
		p1.setName("name1");
118
		masterKeySet.add(p1);
119
		assertEquals(p1.getName(), sdom.get(p1));
120
		p1.setName("name2");
121
		assertEquals(p1.getName(), sdom.get(p1));
122
123
		// Change the detail attribute by changing the master.
124
		SimplePerson p2 = new SimplePerson();
125
		p2.setName("name3");
126
		masterKeySet.add(p2);
127
		assertEquals(p2.getName(), sdom.get(p2));
128
	}
129
130
	public void testPut() {
131
		WritableSet masterKeySet = new WritableSet();
132
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
133
				masterKeySet, BeansObservables.valueFactory("name"),
134
				String.class);
135
136
		// Change the detail attribute explicitly.
137
		SimplePerson person = new SimplePerson();
138
		person.setName("name1");
139
		masterKeySet.add(person);
140
		assertEquals(person.getName(), sdom.get(person));
141
142
		// Set a new name on the detail map.
143
		sdom.put(person, "name2");
144
		// Check that the name has been propagated to the master.
145
		assertEquals("name2", person.getName());
146
		assertEquals(person.getName(), sdom.get(person));
147
	}
148
149
	public void testContainsValue() {
150
		WritableSet masterKeySet = new WritableSet();
151
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
152
				masterKeySet, BeansObservables.valueFactory("name"),
153
				String.class);
154
155
		// Add a person with a given name.
156
		SimplePerson person = new SimplePerson();
157
		person.setName("name");
158
		masterKeySet.add(person);
159
160
		// Make sure the name of the person is contained.
161
		assertTrue(sdom.containsValue(person.getName()));
162
163
		// Remove the person and make sure that it's name cannot be found
164
		// anymore.
165
		masterKeySet.remove(person);
166
		assertFalse(sdom.containsValue(person.getName()));
167
	}
168
169
	public void testRemove() {
170
		WritableSet masterKeySet = new WritableSet();
171
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
172
				masterKeySet, BeansObservables.valueFactory("name"),
173
				String.class);
174
175
		// Add two person objects to the map.
176
		SimplePerson p1 = new SimplePerson();
177
		SimplePerson p2 = new SimplePerson();
178
		masterKeySet.add(p1);
179
		masterKeySet.add(p2);
180
181
		// Initially, both person objects should be contained in the detail map.
182
		assertTrue(sdom.containsKey(p1));
183
		assertTrue(sdom.containsKey(p2));
184
185
		// Remove one person and check that it is not contained anymore.
186
		sdom.remove(p1);
187
		assertFalse(sdom.containsKey(p1));
188
		assertTrue(sdom.containsKey(p2));
189
190
		// Trying to remove a non-existent is allowed but has no effect.
191
		sdom.remove(p1);
192
		assertFalse(sdom.containsKey(p1));
193
		assertTrue(sdom.containsKey(p2));
194
	}
195
196
	public void testDetailObservableChangeEvent() {
197
		WritableSet masterKeySet = new WritableSet();
198
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
199
				masterKeySet, BeansObservables.valueFactory("name"),
200
				String.class);
201
202
		MapChangeEventTracker changeTracker = MapChangeEventTracker
203
				.observe(sdom);
204
205
		SimplePerson person = new SimplePerson();
206
		person.setName("old name");
207
208
		// Initially, we should not have received any event.
209
		assertEquals(0, changeTracker.count);
210
211
		// Add the person and check that we receive an addition event on the
212
		// correct index and with the correct value.
213
		masterKeySet.add(person);
214
		assertEquals(1, changeTracker.count);
215
		assertEquals(1, changeTracker.event.diff.getAddedKeys().size());
216
		assertEquals(0, changeTracker.event.diff.getRemovedKeys().size());
217
		assertEquals(0, changeTracker.event.diff.getChangedKeys().size());
218
		assertSame(person, changeTracker.event.diff.getAddedKeys().iterator()
219
				.next());
220
		assertNull(changeTracker.event.diff.getOldValue(person));
221
		assertEquals("old name", changeTracker.event.diff.getNewValue(person));
222
223
		// Change the detail property and check that we receive a replace
224
		person.setName("new name");
225
		assertEquals(2, changeTracker.count);
226
		assertEquals(0, changeTracker.event.diff.getAddedKeys().size());
227
		assertEquals(0, changeTracker.event.diff.getRemovedKeys().size());
228
		assertEquals(1, changeTracker.event.diff.getChangedKeys().size());
229
		assertSame(person, changeTracker.event.diff.getChangedKeys().iterator()
230
				.next());
231
		assertEquals("old name", changeTracker.event.diff.getOldValue(person));
232
		assertEquals("new name", changeTracker.event.diff.getNewValue(person));
233
	}
234
235
	public void testMasterNull() {
236
		WritableSet masterKeySet = new WritableSet();
237
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
238
				masterKeySet, BeansObservables.valueFactory("name"),
239
				String.class);
240
241
		// Make sure null values are handled gracefully.
242
		masterKeySet.add(null);
243
		assertEquals(1, sdom.size());
244
		assertNull(sdom.get(null));
245
	}
246
247
	public void testDetailObservableValuesAreDisposed() {
248
		final Map detailObservables = new HashMap();
249
		IObservableFactory detailValueFactory = new IObservableFactory() {
250
			public IObservable createObservable(Object target) {
251
				WritableValue detailObservable = new WritableValue();
252
				// Remember the created observables.
253
				detailObservables.put(target, detailObservable);
254
				return detailObservable;
255
			}
256
		};
257
258
		WritableSet masterKeySet = new WritableSet();
259
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
260
				masterKeySet, detailValueFactory, null);
261
262
		Object master1 = new Object();
263
		Object master2 = new Object();
264
		masterKeySet.add(master1);
265
		masterKeySet.add(master2);
266
267
		// Attach a listener in order to ensure that all detail observables are
268
		// actually created.
269
		MapChangeEventTracker.observe(sdom);
270
271
		assertEquals(sdom.size(), detailObservables.size());
272
273
		// No detail observables should be disposed yet.
274
		assertFalse(((WritableValue) detailObservables.get(master1))
275
				.isDisposed());
276
		assertFalse(((WritableValue) detailObservables.get(master2))
277
				.isDisposed());
278
279
		// Only the detail observable for the removed master should be disposed.
280
		masterKeySet.remove(master2);
281
		assertFalse(((WritableValue) detailObservables.get(master1))
282
				.isDisposed());
283
		assertTrue(((WritableValue) detailObservables.get(master2))
284
				.isDisposed());
285
286
		// After disposing the detail map, all detail observables should be
287
		// disposed.
288
		sdom.dispose();
289
		assertTrue(((WritableValue) detailObservables.get(master1))
290
				.isDisposed());
291
		assertTrue(((WritableValue) detailObservables.get(master2))
292
				.isDisposed());
293
	}
294
295
	public void testDisposeOnMasterDisposed() {
296
		WritableSet masterKeySet = new WritableSet();
297
		SetDetailValueObservableMap sdom = new SetDetailValueObservableMap(
298
				masterKeySet, BeansObservables.valueFactory("name"),
299
				String.class);
300
301
		// Initially, nothing should be disposed.
302
		assertFalse(masterKeySet.isDisposed());
303
		assertFalse(sdom.isDisposed());
304
305
		// Upon disposing the master list, the detail list should be disposed as
306
		// well.
307
		masterKeySet.dispose();
308
		assertTrue(masterKeySet.isDisposed());
309
		assertTrue(sdom.isDisposed());
310
	}
311
}
(-)src/org/eclipse/jface/tests/databinding/BindingTestSuite.java (-1 / +7 lines)
Lines 17-23 Link Here
17
 *                    246103, 249992, 256150, 256543, 262269, 175735, 262946,
17
 *                    246103, 249992, 256150, 256543, 262269, 175735, 262946,
18
 *                    255734, 263693, 169876, 266038, 268336, 270461, 271720,
18
 *                    255734, 263693, 169876, 266038, 268336, 270461, 271720,
19
 *                    283204, 281723, 283428
19
 *                    283204, 281723, 283428
20
 *     Ovidio Mallo - bugs 237163, 235195, 299619
20
 *     Ovidio Mallo - bugs 237163, 235195, 299619, 305367
21
 *******************************************************************************/
21
 *******************************************************************************/
22
package org.eclipse.jface.tests.databinding;
22
package org.eclipse.jface.tests.databinding;
23
23
Lines 142-147 Link Here
142
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableMapTest;
142
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableMapTest;
143
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableSetTest;
143
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableSetTest;
144
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableValueTest;
144
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableValueTest;
145
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.ListDetailValueObservableListTest;
146
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.MapDetailValueObservableMapTest;
147
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.SetDetailValueObservableMapTest;
145
import org.eclipse.core.tests.internal.databinding.property.value.ListSimpleValueObservableListTest;
148
import org.eclipse.core.tests.internal.databinding.property.value.ListSimpleValueObservableListTest;
146
import org.eclipse.core.tests.internal.databinding.property.value.MapSimpleValueObservableMapTest;
149
import org.eclipse.core.tests.internal.databinding.property.value.MapSimpleValueObservableMapTest;
147
import org.eclipse.core.tests.internal.databinding.property.value.SetSimpleValueObservableMapTest;
150
import org.eclipse.core.tests.internal.databinding.property.value.SetSimpleValueObservableMapTest;
Lines 384-389 Link Here
384
		addTestSuite(DetailObservableMapTest.class);
387
		addTestSuite(DetailObservableMapTest.class);
385
		addTest(DetailObservableSetTest.suite());
388
		addTest(DetailObservableSetTest.suite());
386
		addTest(DetailObservableValueTest.suite());
389
		addTest(DetailObservableValueTest.suite());
390
		addTest(ListDetailValueObservableListTest.suite());
391
		addTest(MapDetailValueObservableMapTest.suite());
392
		addTest(SetDetailValueObservableMapTest.suite());
387
393
388
		// org.eclipse.core.tests.internal.databinding.property.value
394
		// org.eclipse.core.tests.internal.databinding.property.value
389
		addTestSuite(MapSimpleValueObservableMapTest.class);
395
		addTestSuite(MapSimpleValueObservableMapTest.class);

Return to bug 305367