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

Collapse All | Expand All

(-)src/org/eclipse/jface/examples/databinding/snippets/Snippet025TableViewerWithPropertyDerivedColumns.java (-7 / +61 lines)
Lines 14-19 Link Here
14
14
15
import java.beans.PropertyChangeListener;
15
import java.beans.PropertyChangeListener;
16
import java.beans.PropertyChangeSupport;
16
import java.beans.PropertyChangeSupport;
17
import java.util.ArrayList;
18
import java.util.Arrays;
19
import java.util.List;
17
20
18
import org.eclipse.core.databinding.DataBindingContext;
21
import org.eclipse.core.databinding.DataBindingContext;
19
import org.eclipse.core.databinding.beans.BeanProperties;
22
import org.eclipse.core.databinding.beans.BeanProperties;
Lines 21-30 Link Here
21
import org.eclipse.core.databinding.observable.list.IObservableList;
24
import org.eclipse.core.databinding.observable.list.IObservableList;
22
import org.eclipse.core.databinding.observable.list.WritableList;
25
import org.eclipse.core.databinding.observable.list.WritableList;
23
import org.eclipse.core.databinding.observable.value.IObservableValue;
26
import org.eclipse.core.databinding.observable.value.IObservableValue;
27
import org.eclipse.core.databinding.property.list.ListReducers;
28
import org.eclipse.core.databinding.property.value.IValueProperty;
24
import org.eclipse.jface.databinding.swt.SWTObservables;
29
import org.eclipse.jface.databinding.swt.SWTObservables;
25
import org.eclipse.jface.databinding.swt.WidgetProperties;
30
import org.eclipse.jface.databinding.swt.WidgetProperties;
26
import org.eclipse.jface.databinding.viewers.ViewerSupport;
27
import org.eclipse.jface.databinding.viewers.ViewerProperties;
31
import org.eclipse.jface.databinding.viewers.ViewerProperties;
32
import org.eclipse.jface.databinding.viewers.ViewerSupport;
28
import org.eclipse.jface.layout.GridDataFactory;
33
import org.eclipse.jface.layout.GridDataFactory;
29
import org.eclipse.jface.layout.GridLayoutFactory;
34
import org.eclipse.jface.layout.GridLayoutFactory;
30
import org.eclipse.jface.viewers.ComboViewer;
35
import org.eclipse.jface.viewers.ComboViewer;
Lines 106-116 Link Here
106
		String name = "Donald Duck";
111
		String name = "Donald Duck";
107
		Person mother;
112
		Person mother;
108
		Person father;
113
		Person father;
114
		List children = new ArrayList();
109
115
110
		public Person(String name, Person mother, Person father) {
116
		public Person(String name, Person mother, Person father) {
111
			this.name = name;
117
			this.name = name;
112
			this.mother = mother;
118
			this.mother = mother;
113
			this.father = father;
119
			this.father = father;
120
			if (mother != null) {
121
				mother.addChild(this);
122
			}
123
			if (father != null) {
124
				father.addChild(this);
125
			}
126
		}
127
128
		private void addChild(Person person) {
129
			children.add(person);
130
			firePropertyChange("children", null, children);
131
		}
132
133
		private void removeChild(Person person) {
134
			children.remove(person);
135
			firePropertyChange("children", null, children);
114
		}
136
		}
115
137
116
		public String getName() {
138
		public String getName() {
Lines 125-132 Link Here
125
			return mother;
147
			return mother;
126
		}
148
		}
127
149
150
		public Person[] getChildren() {
151
			return (Person[]) children.toArray(new Person[children.size()]);
152
		}
153
128
		public void setMother(Person mother) {
154
		public void setMother(Person mother) {
155
			if (this.mother != null) {
156
				this.mother.removeChild(this);
157
			}
129
			firePropertyChange("mother", this.mother, this.mother = mother);
158
			firePropertyChange("mother", this.mother, this.mother = mother);
159
			if (this.mother != null) {
160
				this.mother.addChild(this);
161
			}
130
		}
162
		}
131
163
132
		public Person getFather() {
164
		public Person getFather() {
Lines 134-140 Link Here
134
		}
166
		}
135
167
136
		public void setFather(Person father) {
168
		public void setFather(Person father) {
169
			if (this.father != null) {
170
				this.father.removeChild(this);
171
			}
137
			firePropertyChange("father", this.father, this.father = father);
172
			firePropertyChange("father", this.father, this.father = father);
173
			if (this.father != null) {
174
				this.father.addChild(this);
175
			}
138
		}
176
		}
139
	}
177
	}
140
178
Lines 145-150 Link Here
145
	// data access tier. Since this snippet doesn't have any persistent objects
183
	// data access tier. Since this snippet doesn't have any persistent objects
146
	// ro retrieve, this ViewModel just instantiates a model object to edit.
184
	// ro retrieve, this ViewModel just instantiates a model object to edit.
147
	static class ViewModel {
185
	static class ViewModel {
186
148
		// The model to bind
187
		// The model to bind
149
		private IObservableList people = new WritableList();
188
		private IObservableList people = new WritableList();
150
		{
189
		{
Lines 197-202 Link Here
197
			createColumn("Maternal Grandfather");
236
			createColumn("Maternal Grandfather");
198
			createColumn("Paternal Grandmother");
237
			createColumn("Paternal Grandmother");
199
			createColumn("Paternal Grandfather");
238
			createColumn("Paternal Grandfather");
239
			createColumn("Children").setWidth(200);
200
240
201
			duckFamily.setLinesVisible(true);
241
			duckFamily.setLinesVisible(true);
202
242
Lines 216-232 Link Here
216
256
217
			GridLayoutFactory.swtDefaults().numColumns(2).applyTo(shell);
257
			GridLayoutFactory.swtDefaults().numColumns(2).applyTo(shell);
218
			// Open and return the Shell
258
			// Open and return the Shell
219
			shell.setSize(800, 300);
259
			shell.setSize(1000, 300);
220
			shell.open();
260
			shell.open();
221
			return shell;
261
			return shell;
222
		}
262
		}
223
263
224
		private void createColumn(String string) {
264
		private TableColumn createColumn(String string) {
225
			final TableColumn column = new TableColumn(duckFamily, SWT.NONE);
265
			final TableColumn column = new TableColumn(duckFamily, SWT.NONE);
226
			column.setText(string);
266
			column.setText(string);
227
			column.pack();
267
			column.pack();
228
			if (column.getWidth() < 100)
268
			if (column.getWidth() < 100)
229
				column.setWidth(100);
269
				column.setWidth(100);
270
			return column;
230
		}
271
		}
231
272
232
		protected void bindGUI(DataBindingContext dbc) {
273
		protected void bindGUI(DataBindingContext dbc) {
Lines 240-249 Link Here
240
			});
281
			});
241
282
242
			ViewerSupport.bind(peopleViewer, viewModel.getPeople(),
283
			ViewerSupport.bind(peopleViewer, viewModel.getPeople(),
243
					BeanProperties.values(Person.class, new String[] { "name",
284
					getProperties());
244
							"mother.name", "father.name", "mother.mother.name",
245
							"mother.father.name", "father.mother.name",
246
							"father.father.name" }));
247
285
248
			IObservableValue masterSelection = ViewerProperties
286
			IObservableValue masterSelection = ViewerProperties
249
					.singleSelection().observe(peopleViewer);
287
					.singleSelection().observe(peopleViewer);
Lines 269-272 Link Here
269
					"father").observeDetail(masterSelection));
307
					"father").observeDetail(masterSelection));
270
		}
308
		}
271
	}
309
	}
310
311
	private static IValueProperty[] getProperties() {
312
		List<IValueProperty> properties = new ArrayList<IValueProperty>();
313
314
		// Add the "simple" properties.
315
		properties.addAll(Arrays.asList(BeanProperties.values(Person.class,
316
				new String[] { "name", "mother.name", "father.name",
317
						"mother.mother.name", "mother.father.name",
318
						"father.mother.name", "father.father.name" })));
319
320
		// Add the "complex" property.
321
		properties.add(BeanProperties.list("children").values("name").reduce(
322
				ListReducers.join(", ")));
323
324
		return properties.toArray(new IValueProperty[properties.size()]);
325
	}
272
}
326
}
(-)src/org/eclipse/core/databinding/observable/masterdetail/MasterDetailObservables.java (-1 / +87 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 - bug 300043
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
	 * Creates an unmodifiable observable list of detail values from an
155
	 * observable list of observable values. For every master value in the given
156
	 * list, the provided factory is used to create the corresponding detail
157
	 * observable value. This can e.g. be used to create a list of values that
158
	 * represent the same property of a set of selected objects in a table.
159
	 * 
160
	 * @param masterList
161
	 *            the list of master values to track
162
	 * @param detailFactory
163
	 *            a factory for creating {@link IObservableValue} instances
164
	 *            given a current value of one of the masters
165
	 * @param detailType
166
	 *            the value type of the detail values, typically of type
167
	 *            java.lang.Class and can be <code>null</code>
168
	 * @return a list of detail values of the given value type that, for any
169
	 *         current value of any of the master values, behaves like the
170
	 *         observable value created by the factory for that current value.
171
	 * 
172
	 * @since 1.3
173
	 */
174
	public static IObservableList detailListValues(IObservableList masterList,
175
			IObservableFactory detailFactory, Object detailType) {
176
		return new ListDetailValueObservableList(masterList, detailFactory,
177
				detailType);
178
	}
179
180
	/**
181
	 * Creates an unmodifiable observable map of detail values from an
182
	 * observable set of master values. For every master value in the given set,
183
	 * the provided factory is used to create the corresponding detail
184
	 * observable value which is added to a map whose key is the master value.
185
	 * This can e.g. be used to create a list of values that represent the same
186
	 * property of a set of selected objects in a table.
187
	 * 
188
	 * @param masterSet
189
	 *            the set of master values to track
190
	 * @param detailFactory
191
	 *            a factory for creating {@link IObservableValue} instances
192
	 *            given a current value of one of the masters
193
	 * @param detailType
194
	 *            the value type of the detail values, typically of type
195
	 *            java.lang.Class and can be <code>null</code>
196
	 * @return a map of detail values of the given value type that, for any
197
	 *         current value of any of the master values, behaves like the
198
	 *         observable value created by the factory for that current value.
199
	 * 
200
	 * @since 1.3
201
	 */
202
	public static IObservableMap detailSetValues(IObservableSet masterSet,
203
			IObservableFactory detailFactory, Object detailType) {
204
		return new SetDetailValueObservableMap(masterSet, detailFactory,
205
				detailType);
206
	}
207
208
	/**
209
	 * Creates an unmodifiable observable map of detail values from an
210
	 * observable map of master values. For every master value in the given map,
211
	 * the provided factory is used to create the corresponding detail
212
	 * observable value. This can e.g. be used to create a map of values that
213
	 * represent the same property of a set of selected objects in a table.
214
	 * 
215
	 * @param masterMap
216
	 *            the set of master values to track
217
	 * @param detailFactory
218
	 *            a factory for creating {@link IObservableValue} instances
219
	 *            given a current value of one of the masters
220
	 * @param detailType
221
	 *            the value type of the detail values, typically of type
222
	 *            java.lang.Class and can be <code>null</code>
223
	 * @return a list of detail values of the given value type that, for any
224
	 *         current value of any of the master values, behaves like the
225
	 *         observable value created by the factory for that current value.
226
	 * 
227
	 * @since 1.3
228
	 */
229
	public static IObservableMap detailMapValues(IObservableMap masterMap,
230
			IObservableFactory detailFactory, Object detailType) {
231
		return new MapDetailValueObservableMap(masterMap, detailFactory,
232
				detailType);
233
	}
148
}
234
}
(-)src/org/eclipse/core/internal/databinding/observable/masterdetail/ListDetailValueObservableList.java (+268 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 300043)
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.IdentitySet;
39
40
/**
41
 * @since 1.3
42
 */
43
public class ListDetailValueObservableList extends AbstractObservableList
44
		implements IObserving, RandomAccess {
45
46
	private IObservableList masterList;
47
48
	private IObservableFactory detailFactory;
49
50
	private Object detailType;
51
52
	private ArrayList detailList;
53
54
	private IdentitySet staleDetailObservables = new IdentitySet();
55
56
	private IListChangeListener masterListListener = new IListChangeListener() {
57
		public void handleListChange(ListChangeEvent event) {
58
			adaptMasterListDiff(event.diff);
59
		}
60
	};
61
62
	private IValueChangeListener detailValueListener = new IValueChangeListener() {
63
		public void handleValueChange(ValueChangeEvent event) {
64
			if (!event.getObservable().isStale()) {
65
				staleDetailObservables.remove(event.getObservable());
66
			}
67
			handleDetailValueChange(event);
68
		}
69
	};
70
71
	private IStaleListener masterStaleListener = new IStaleListener() {
72
		public void handleStale(StaleEvent staleEvent) {
73
			fireStale();
74
		}
75
	};
76
77
	private IStaleListener detailStaleListener = new IStaleListener() {
78
		public void handleStale(StaleEvent staleEvent) {
79
			boolean wasStale = isStale();
80
			staleDetailObservables.add((staleEvent.getObservable()));
81
			if (!wasStale) {
82
				fireStale();
83
			}
84
		}
85
	};
86
87
	/**
88
	 * 
89
	 * @param masterList
90
	 * @param detailFactory
91
	 * @param detailType
92
	 */
93
	public ListDetailValueObservableList(IObservableList masterList,
94
			IObservableFactory detailFactory, Object detailType) {
95
		super(masterList.getRealm());
96
		this.masterList = masterList;
97
		this.detailFactory = detailFactory;
98
		this.detailType = detailType;
99
		this.detailList = new ArrayList();
100
101
		masterList.addListChangeListener(masterListListener);
102
		masterList.addStaleListener(masterStaleListener);
103
		masterList.addDisposeListener(new IDisposeListener() {
104
			public void handleDispose(DisposeEvent event) {
105
				ListDetailValueObservableList.this.dispose();
106
			}
107
		});
108
109
		ListDiff initMasterDiff = Diffs.computeListDiff(Collections.EMPTY_LIST,
110
				masterList);
111
		adaptMasterListDiff(initMasterDiff);
112
	}
113
114
	private void adaptMasterListDiff(ListDiff masterListDiff) {
115
		boolean wasStale = isStale();
116
117
		ListDiffEntry[] masterEntries = masterListDiff.getDifferences();
118
		ListDiffEntry[] detailEntries = new ListDiffEntry[masterEntries.length];
119
		for (int i = 0; i < masterEntries.length; i++) {
120
			ListDiffEntry masterEntry = masterEntries[i];
121
			int index = masterEntry.getPosition();
122
123
			Object detailValue;
124
			if (masterEntry.isAddition()) {
125
				Object masterElement = masterEntry.getElement();
126
				detailValue = addDetailObservable(masterElement, index);
127
			} else {
128
				detailValue = removeDetailObservable(index);
129
			}
130
131
			// Create the corresponding diff for the detail list.
132
			detailEntries[i] = Diffs.createListDiffEntry(index, masterEntry
133
					.isAddition(), detailValue);
134
		}
135
136
		if (!wasStale && isStale()) {
137
			fireStale();
138
		}
139
140
		// Fire a list change event with the adapted diffs on the detail list.
141
		fireListChange(Diffs.createListDiff(detailEntries));
142
	}
143
144
	private Object addDetailObservable(Object masterElement, int index) {
145
		IObservableValue detail;
146
		ObservableTracker.setIgnore(true);
147
		try {
148
			detail = (IObservableValue) detailFactory
149
					.createObservable(masterElement);
150
		} finally {
151
			ObservableTracker.setIgnore(false);
152
		}
153
154
		detailList.add(index, detail);
155
156
		detail.addValueChangeListener(detailValueListener);
157
158
		detail.addStaleListener(detailStaleListener);
159
		if (detail.isStale()) {
160
			staleDetailObservables.add(detail);
161
		}
162
163
		return detail.getValue();
164
	}
165
166
	private Object removeDetailObservable(int index) {
167
		IObservableValue detail = (IObservableValue) detailList.remove(index);
168
		staleDetailObservables.remove(detail);
169
		Object detailValue = detail.getValue();
170
		detail.dispose();
171
		return detailValue;
172
	}
173
174
	private void handleDetailValueChange(ValueChangeEvent event) {
175
		IObservableValue detail = event.getObservableValue();
176
177
		BitSet detailIndexes = new BitSet();
178
		for (int i = 0; i < detailList.size(); i++) {
179
			if (detailList.get(i) == detail) {
180
				detailIndexes.set(i);
181
			}
182
		}
183
184
		Object oldValue = event.diff.getOldValue();
185
		Object newValue = event.diff.getNewValue();
186
		ListDiffEntry[] diffEntries = new ListDiffEntry[2 * detailIndexes
187
				.cardinality()];
188
		int diffIndex = 0;
189
		for (int b = 0; b != -1; b = detailIndexes.nextSetBit(b + 1)) {
190
			diffEntries[diffIndex++] = Diffs.createListDiffEntry(b, false,
191
					oldValue);
192
			diffEntries[diffIndex++] = Diffs.createListDiffEntry(b, true,
193
					newValue);
194
		}
195
		fireListChange(Diffs.createListDiff(diffEntries));
196
	}
197
198
	protected int doGetSize() {
199
		return detailList.size();
200
	}
201
202
	public Object get(int index) {
203
		ObservableTracker.getterCalled(this);
204
		return ((IObservableValue) detailList.get(index)).getValue();
205
	}
206
207
	public Object set(int index, Object element) {
208
		IObservableValue detail = (IObservableValue) detailList.get(index);
209
		Object oldElement = detail.getValue();
210
		detail.setValue(element);
211
		return oldElement;
212
	}
213
214
	public Object move(int oldIndex, int newIndex) {
215
		throw new UnsupportedOperationException();
216
	}
217
218
	public boolean remove(Object o) {
219
		throw new UnsupportedOperationException();
220
	}
221
222
	public boolean removeAll(Collection c) {
223
		throw new UnsupportedOperationException();
224
	}
225
226
	public boolean retainAll(Collection c) {
227
		throw new UnsupportedOperationException();
228
	}
229
230
	public Object getElementType() {
231
		return detailType;
232
	}
233
234
	public boolean isStale() {
235
		return super.isStale()
236
				|| (masterList != null && masterList.isStale())
237
				|| (staleDetailObservables != null && !staleDetailObservables
238
						.isEmpty());
239
	}
240
241
	public Object getObserved() {
242
		return masterList;
243
	}
244
245
	public synchronized void dispose() {
246
		if (masterList != null) {
247
			masterList.removeListChangeListener(masterListListener);
248
			masterList.removeStaleListener(masterStaleListener);
249
		}
250
251
		if (detailList != null) {
252
			for (Iterator iter = detailList.iterator(); iter.hasNext();) {
253
				IObservableValue detailValue = (IObservableValue) iter.next();
254
				detailValue.dispose();
255
			}
256
			detailList.clear();
257
		}
258
259
		masterList = null;
260
		detailFactory = null;
261
		detailType = null;
262
		masterListListener = null;
263
		detailValueListener = null;
264
		staleDetailObservables = null;
265
266
		super.dispose();
267
	}
268
}
(-)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 300043)
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.3
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(), event.diff
69
						.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
}
(-)src/org/eclipse/core/internal/databinding/observable/masterdetail/MapDetailValueObservableMap.java (+385 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 300043)
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.HashMap;
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.3
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 IMapChangeListener masterMapListener = new IMapChangeListener() {
56
		public void handleMapChange(MapChangeEvent event) {
57
			adaptMasterMapDiff(event.diff);
58
		}
59
	};
60
61
	private Map detailObservableValueMap = new HashMap();
62
63
	private IdentitySet staleDetailObservables = new IdentitySet();
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
		masterMap.addMapChangeListener(masterMapListener);
91
		masterMap.addStaleListener(masterStaleListener);
92
		masterMap.addDisposeListener(new IDisposeListener() {
93
			public void handleDispose(DisposeEvent event) {
94
				MapDetailValueObservableMap.this.dispose();
95
			}
96
		});
97
98
		MapDiff initMasterDiff = Diffs.computeMapDiff(Collections.EMPTY_MAP,
99
				masterMap);
100
		adaptMasterMapDiff(initMasterDiff);
101
	}
102
103
	private void adaptMasterMapDiff(MapDiff diff) {
104
		IdentityMap oldValues = new IdentityMap();
105
		IdentityMap newValues = new IdentityMap();
106
107
		Set addedKeys = diff.getAddedKeys();
108
		for (Iterator iter = addedKeys.iterator(); iter.hasNext();) {
109
			Object addedKey = iter.next();
110
111
			hookListener(addedKey);
112
113
			IObservableValue detailValue = getDetailObservableValue(addedKey);
114
			newValues.put(addedKey, detailValue.getValue());
115
		}
116
117
		Set removedKeys = diff.getRemovedKeys();
118
		for (Iterator iter = removedKeys.iterator(); iter.hasNext();) {
119
			Object removedKey = iter.next();
120
121
			unhookListener(removedKey);
122
123
			IObservableValue detailValue = getDetailObservableValue(removedKey);
124
			oldValues.put(removedKey, detailValue.getValue());
125
		}
126
127
		Set changedKeys = diff.getChangedKeys();
128
		for (Iterator iter = changedKeys.iterator(); iter.hasNext();) {
129
			Object changedKey = iter.next();
130
131
			IObservableValue oldDetailValue = getDetailObservableValue(changedKey);
132
			oldValues.put(changedKey, oldDetailValue.getValue());
133
134
			unhookListener(changedKey);
135
			hookListener(changedKey);
136
137
			IObservableValue newDetailValue = getDetailObservableValue(changedKey);
138
			newValues.put(changedKey, newDetailValue.getValue());
139
		}
140
141
		fireMapChange(Diffs.createMapDiff(addedKeys, removedKeys, changedKeys,
142
				oldValues, newValues));
143
	}
144
145
	private void hookListener(final Object addedKey) {
146
		final IObservableValue detailValue = getDetailObservableValue(addedKey);
147
148
		detailValue.addValueChangeListener(new IValueChangeListener() {
149
			public void handleValueChange(ValueChangeEvent event) {
150
				if (!event.getObservableValue().isStale()) {
151
					staleDetailObservables.remove(detailValue);
152
				}
153
154
				fireMapChange(Diffs.createMapDiffSingleChange(addedKey,
155
						event.diff.getOldValue(), event.diff.getNewValue()));
156
			}
157
		});
158
159
		detailValue.addStaleListener(detailStaleListener);
160
	}
161
162
	private void unhookListener(Object removedKey) {
163
		if (isDisposed()) {
164
			return;
165
		}
166
167
		IObservableValue detailValue = (IObservableValue) detailObservableValueMap
168
				.remove(removedKey);
169
		staleDetailObservables.remove(detailValue);
170
		detailValue.dispose();
171
	}
172
173
	private IObservableValue getDetailObservableValue(Object masterKey) {
174
		IObservableValue detailValue = (IObservableValue) detailObservableValueMap
175
				.get(masterKey);
176
177
		if (detailValue == null) {
178
			ObservableTracker.setIgnore(true);
179
			try {
180
				detailValue = (IObservableValue) observableValueFactory
181
						.createObservable(masterMap.get(masterKey));
182
			} finally {
183
				ObservableTracker.setIgnore(false);
184
			}
185
186
			detailObservableValueMap.put(masterKey, detailValue);
187
188
			if (detailValue.isStale()) {
189
				addStaleDetailObservable(detailValue);
190
			}
191
		}
192
193
		return detailValue;
194
	}
195
196
	private void addStaleDetailObservable(IObservableValue detailObservable) {
197
		boolean wasStale = isStale();
198
		staleDetailObservables.add(detailObservable);
199
		if (!wasStale) {
200
			fireStale();
201
		}
202
	}
203
204
	public Set keySet() {
205
		getterCalled();
206
207
		return masterMap.keySet();
208
	}
209
210
	public Object get(Object key) {
211
		getterCalled();
212
213
		if (!containsKey(key)) {
214
			return null;
215
		}
216
217
		IObservableValue detailValue = getDetailObservableValue(key);
218
		return detailValue.getValue();
219
	}
220
221
	public Object put(Object key, Object value) {
222
		if (!containsKey(key)) {
223
			// TODO omallo: Better throw an exception?
224
			return null;
225
		}
226
227
		IObservableValue detailValue = getDetailObservableValue(key);
228
		Object oldValue = detailValue.getValue();
229
		detailValue.setValue(value);
230
		return oldValue;
231
	}
232
233
	public boolean containsKey(Object key) {
234
		getterCalled();
235
236
		return masterMap.containsKey(key);
237
	}
238
239
	public Object remove(Object key) {
240
		checkRealm();
241
242
		if (!containsKey(key)) {
243
			return null;
244
		}
245
246
		IObservableValue detailValue = getDetailObservableValue(key);
247
		Object oldValue = detailValue.getValue();
248
249
		masterMap.remove(key);
250
251
		return oldValue;
252
	}
253
254
	public int size() {
255
		getterCalled();
256
257
		return masterMap.size();
258
	}
259
260
	public boolean isStale() {
261
		return super.isStale()
262
				|| (masterMap != null && masterMap.isStale())
263
				|| (staleDetailObservables != null && !staleDetailObservables
264
						.isEmpty());
265
	}
266
267
	public Object getKeyType() {
268
		return masterMap.getKeyType();
269
	}
270
271
	public Object getValueType() {
272
		return detailValueType;
273
	}
274
275
	public Object getObserved() {
276
		return masterMap;
277
	}
278
279
	public synchronized void dispose() {
280
		if (masterMap != null) {
281
			masterMap.removeMapChangeListener(masterMapListener);
282
			masterMap.removeStaleListener(masterStaleListener);
283
		}
284
285
		if (detailObservableValueMap != null) {
286
			for (Iterator iter = detailObservableValueMap.values().iterator(); iter
287
					.hasNext();) {
288
				IObservableValue detailValue = (IObservableValue) iter.next();
289
				detailValue.dispose();
290
			}
291
			detailObservableValueMap.clear();
292
		}
293
294
		masterMap = null;
295
		observableValueFactory = null;
296
		detailValueType = null;
297
		detailObservableValueMap = null;
298
		masterStaleListener = null;
299
		detailStaleListener = null;
300
		staleDetailObservables = null;
301
302
		super.dispose();
303
	}
304
305
	public Set entrySet() {
306
		getterCalled();
307
308
		if (entrySet == null) {
309
			entrySet = new EntrySet();
310
		}
311
		return entrySet;
312
	}
313
314
	private void getterCalled() {
315
		ObservableTracker.getterCalled(this);
316
	}
317
318
	private class EntrySet extends AbstractSet {
319
320
		public Iterator iterator() {
321
			final Iterator keyIterator = keySet().iterator();
322
			return new Iterator() {
323
324
				public boolean hasNext() {
325
					return keyIterator.hasNext();
326
				}
327
328
				public Object next() {
329
					Object key = keyIterator.next();
330
					return new MapEntry(key);
331
				}
332
333
				public void remove() {
334
					keyIterator.remove();
335
				}
336
			};
337
		}
338
339
		public int size() {
340
			return MapDetailValueObservableMap.this.size();
341
		}
342
	}
343
344
	private final class MapEntry implements Map.Entry {
345
346
		private final Object key;
347
348
		private MapEntry(Object key) {
349
			this.key = key;
350
		}
351
352
		public Object getKey() {
353
			MapDetailValueObservableMap.this.getterCalled();
354
			return key;
355
		}
356
357
		public Object getValue() {
358
			return MapDetailValueObservableMap.this.get(getKey());
359
		}
360
361
		public Object setValue(Object value) {
362
			return MapDetailValueObservableMap.this.put(getKey(), value);
363
		}
364
365
		public boolean equals(Object o) {
366
			MapDetailValueObservableMap.this.getterCalled();
367
			if (o == this)
368
				return true;
369
			if (o == null)
370
				return false;
371
			if (!(o instanceof Map.Entry))
372
				return false;
373
			Map.Entry that = (Map.Entry) o;
374
			return Util.equals(this.getKey(), that.getKey())
375
					&& Util.equals(this.getValue(), that.getValue());
376
		}
377
378
		public int hashCode() {
379
			MapDetailValueObservableMap.this.getterCalled();
380
			Object value = getValue();
381
			return (getKey() == null ? 0 : getKey().hashCode())
382
					^ (value == null ? 0 : value.hashCode());
383
		}
384
	}
385
}
(-)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, 300043
21
 *******************************************************************************/
21
 *******************************************************************************/
22
package org.eclipse.jface.tests.databinding;
22
package org.eclipse.jface.tests.databinding;
23
23
Lines 140-145 Link Here
140
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableMapTest;
140
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableMapTest;
141
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableSetTest;
141
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableSetTest;
142
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableValueTest;
142
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.DetailObservableValueTest;
143
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.ListDetailValueObservableListTest;
144
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.MapDetailValueObservableMapTest;
145
import org.eclipse.core.tests.internal.databinding.observable.masterdetail.SetDetailValueObservableMapTest;
143
import org.eclipse.core.tests.internal.databinding.property.value.ListSimpleValueObservableListTest;
146
import org.eclipse.core.tests.internal.databinding.property.value.ListSimpleValueObservableListTest;
144
import org.eclipse.core.tests.internal.databinding.property.value.MapSimpleValueObservableMapTest;
147
import org.eclipse.core.tests.internal.databinding.property.value.MapSimpleValueObservableMapTest;
145
import org.eclipse.core.tests.internal.databinding.property.value.SetSimpleValueObservableMapTest;
148
import org.eclipse.core.tests.internal.databinding.property.value.SetSimpleValueObservableMapTest;
Lines 380-385 Link Here
380
		addTestSuite(DetailObservableMapTest.class);
383
		addTestSuite(DetailObservableMapTest.class);
381
		addTest(DetailObservableSetTest.suite());
384
		addTest(DetailObservableSetTest.suite());
382
		addTest(DetailObservableValueTest.suite());
385
		addTest(DetailObservableValueTest.suite());
386
		addTest(ListDetailValueObservableListTest.suite());
387
		addTest(MapDetailValueObservableMapTest.suite());
388
		addTest(SetDetailValueObservableMapTest.suite());
383
389
384
		// org.eclipse.core.tests.internal.databinding.property.value
390
		// org.eclipse.core.tests.internal.databinding.property.value
385
		addTestSuite(MapSimpleValueObservableMapTest.class);
391
		addTestSuite(MapSimpleValueObservableMapTest.class);
(-)src/org/eclipse/core/tests/internal/databinding/observable/masterdetail/ListDetailValueObservableListTest.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 300043)
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(ListDetailValueObservableListTest.class
46
				.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 testDetailObservableChangeEvent() {
198
		WritableList masterList = new WritableList();
199
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
200
				masterList, BeansObservables.valueFactory("name"), String.class);
201
202
		ListChangeEventTracker changeTracker = ListChangeEventTracker
203
				.observe(ldol);
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
		masterList.add(person);
214
		assertEquals(1, changeTracker.count);
215
		assertEquals(1, changeTracker.event.diff.getDifferences().length);
216
		assertTrue(changeTracker.event.diff.getDifferences()[0].isAddition());
217
		assertEquals(0, changeTracker.event.diff.getDifferences()[0]
218
				.getPosition());
219
		assertEquals(person.getName(), changeTracker.event.diff
220
				.getDifferences()[0].getElement());
221
222
		// Change the detail property and check that we receive a replace event.
223
		person.setName("new name");
224
		assertEquals(2, changeTracker.count);
225
		assertIsSingleReplaceDiff(changeTracker.event.diff, 0, "old name",
226
				"new name");
227
	}
228
229
	private void assertIsSingleReplaceDiff(ListDiff diff, int index,
230
			Object oldElement, Object newElement) {
231
		// We should have 2 diff entries.
232
		assertEquals(2, diff.getDifferences().length);
233
234
		ListDiffEntry entry1 = diff.getDifferences()[0];
235
		ListDiffEntry entry2 = diff.getDifferences()[1];
236
237
		// One diff entry must be an addition, the other a removal.
238
		assertTrue(entry1.isAddition() != entry2.isAddition());
239
240
		// Check for the index on the diff entries.
241
		assertEquals(index, entry1.getPosition());
242
		assertEquals(index, entry2.getPosition());
243
244
		// Check for the old/new element values on both diff entries.
245
		if (entry1.isAddition()) {
246
			assertEquals(oldElement, entry2.getElement());
247
			assertEquals(newElement, entry1.getElement());
248
		} else {
249
			assertEquals(oldElement, entry1.getElement());
250
			assertEquals(newElement, entry2.getElement());
251
		}
252
	}
253
254
	public void testMasterNull() {
255
		WritableList masterObservableList = new WritableList();
256
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
257
				masterObservableList, BeansObservables.valueFactory("name"),
258
				String.class);
259
260
		// Make sure null values are handled gracefully.
261
		masterObservableList.add(null);
262
		assertEquals(1, ldol.size());
263
		assertNull(ldol.get(0));
264
	}
265
266
	public void testDetailObservableValuesAreDisposed() {
267
		final List detailObservables = new ArrayList();
268
		IObservableFactory detailValueFactory = new IObservableFactory() {
269
			public IObservable createObservable(Object target) {
270
				WritableValue detailObservable = new WritableValue();
271
				// Remember the created observables.
272
				detailObservables.add(detailObservable);
273
				return detailObservable;
274
			}
275
		};
276
277
		WritableList masterList = new WritableList();
278
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
279
				masterList, detailValueFactory, null);
280
281
		masterList.add(new Object());
282
		masterList.add(new Object());
283
284
		assertEquals(ldol.size(), detailObservables.size());
285
286
		// No detail observables should be disposed yet.
287
		assertFalse(((WritableValue) detailObservables.get(0)).isDisposed());
288
		assertFalse(((WritableValue) detailObservables.get(1)).isDisposed());
289
290
		// Only the detail observable for the removed master should be disposed.
291
		masterList.remove(1);
292
		assertFalse(((WritableValue) detailObservables.get(0)).isDisposed());
293
		assertTrue(((WritableValue) detailObservables.get(1)).isDisposed());
294
295
		// After disposing the detail list, all detail observables should be
296
		// disposed.
297
		ldol.dispose();
298
		assertTrue(((WritableValue) detailObservables.get(0)).isDisposed());
299
		assertTrue(((WritableValue) detailObservables.get(1)).isDisposed());
300
	}
301
302
	public void testDisposeOnMasterDisposed() {
303
		WritableList masterList = new WritableList();
304
		ListDetailValueObservableList ldol = new ListDetailValueObservableList(
305
				masterList, BeansObservables.valueFactory("name"), String.class);
306
307
		// Initially, nothing should be disposed.
308
		assertFalse(masterList.isDisposed());
309
		assertFalse(ldol.isDisposed());
310
311
		// Upon disposing the master list, the detail list should be disposed as
312
		// well.
313
		masterList.dispose();
314
		assertTrue(masterList.isDisposed());
315
		assertTrue(ldol.isDisposed());
316
	}
317
318
	private static class Delegate extends
319
			AbstractObservableCollectionContractDelegate {
320
		public IObservableCollection createObservableCollection(Realm realm,
321
				int elementCount) {
322
			WritableList masterList = new WritableList(realm);
323
			for (int i = 0; i < elementCount; i++) {
324
				masterList.add(new SimplePerson());
325
			}
326
327
			return new TestListDetailValueObservableList(masterList,
328
					BeansObservables.valueFactory(realm, "name"), String.class);
329
		}
330
331
		public void change(IObservable observable) {
332
			TestListDetailValueObservableList ldol = (TestListDetailValueObservableList) observable;
333
			ldol.masterList.add(new SimplePerson());
334
		}
335
336
		public Object getElementType(IObservableCollection collection) {
337
			return String.class;
338
		}
339
	}
340
341
	private static class TestListDetailValueObservableList extends
342
			ListDetailValueObservableList {
343
		final IObservableList masterList;
344
345
		public TestListDetailValueObservableList(IObservableList masterList,
346
				IObservableFactory detailValueFactory, Object detailType) {
347
			super(masterList, detailValueFactory, detailType);
348
			this.masterList = masterList;
349
		}
350
	}
351
}
(-)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 300043)
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(MapDetailValueObservableMapTest.class
38
				.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 list.
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 list.
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 list and check that we still
104
		// have the name of the second person in the detail list.
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 list, the detail list 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 300043)
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/core/databinding/property/value/ValueProperty.java (-1 / +28 lines)
Lines 7-21 Link Here
7
 *
7
 *
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 - bugs 195222, 300043
11
 ******************************************************************************/
11
 ******************************************************************************/
12
12
13
package org.eclipse.core.databinding.property.value;
13
package org.eclipse.core.databinding.property.value;
14
14
15
import org.eclipse.core.databinding.observable.IObservable;
15
import org.eclipse.core.databinding.observable.IObservable;
16
import org.eclipse.core.databinding.observable.Realm;
16
import org.eclipse.core.databinding.observable.Realm;
17
import org.eclipse.core.databinding.observable.list.IObservableList;
18
import org.eclipse.core.databinding.observable.map.IObservableMap;
17
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
19
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
18
import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
20
import org.eclipse.core.databinding.observable.masterdetail.MasterDetailObservables;
21
import org.eclipse.core.databinding.observable.set.IObservableSet;
19
import org.eclipse.core.databinding.observable.value.IObservableValue;
22
import org.eclipse.core.databinding.observable.value.IObservableValue;
20
import org.eclipse.core.databinding.property.list.IListProperty;
23
import org.eclipse.core.databinding.property.list.IListProperty;
21
import org.eclipse.core.databinding.property.map.IMapProperty;
24
import org.eclipse.core.databinding.property.map.IMapProperty;
Lines 112-117 Link Here
112
				.getRealm()), getValueType());
115
				.getRealm()), getValueType());
113
	}
116
	}
114
117
118
	/**
119
	 * @since 1.3
120
	 */
121
	public IObservableList observeDetail(IObservableList master) {
122
		return MasterDetailObservables.detailListValues(master,
123
				valueFactory(master.getRealm()), getValueType());
124
	}
125
126
	/**
127
	 * @since 1.3
128
	 */
129
	public IObservableMap observeDetail(IObservableSet master) {
130
		return MasterDetailObservables.detailSetValues(master,
131
				valueFactory(master.getRealm()), getValueType());
132
	}
133
134
	/**
135
	 * @since 1.3
136
	 */
137
	public IObservableMap observeDetail(IObservableMap master) {
138
		return MasterDetailObservables.detailMapValues(master,
139
				valueFactory(master.getRealm()), getValueType());
140
	}
141
115
	public final IValueProperty value(IValueProperty detailValue) {
142
	public final IValueProperty value(IValueProperty detailValue) {
116
		return new ValuePropertyDetailValue(this, detailValue);
143
		return new ValuePropertyDetailValue(this, detailValue);
117
	}
144
	}
(-)src/org/eclipse/core/databinding/property/list/IListProperty.java (-1 / +9 lines)
Lines 7-13 Link Here
7
 *
7
 *
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, 300043
11
 ******************************************************************************/
11
 ******************************************************************************/
12
12
13
package org.eclipse.core.databinding.property.list;
13
package org.eclipse.core.databinding.property.list;
Lines 161-164 Link Here
161
	 *         properties
161
	 *         properties
162
	 */
162
	 */
163
	public IListProperty values(IValueProperty detailValue);
163
	public IListProperty values(IValueProperty detailValue);
164
165
	/**
166
	 * @param reducer
167
	 *            the strategy that reduces the list down to a value.
168
	 * @return a read-only reduced value from this list property
169
	 * @since 1.3
170
	 */
171
	public IValueProperty reduce(IListReducer reducer);
164
}
172
}
(-)src/org/eclipse/core/databinding/property/list/ListProperty.java (-1 / +9 lines)
Lines 7-13 Link Here
7
 *
7
 *
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, 300043
11
 ******************************************************************************/
11
 ******************************************************************************/
12
12
13
package org.eclipse.core.databinding.property.list;
13
package org.eclipse.core.databinding.property.list;
Lines 26-31 Link Here
26
import org.eclipse.core.databinding.observable.value.IObservableValue;
26
import org.eclipse.core.databinding.observable.value.IObservableValue;
27
import org.eclipse.core.databinding.property.value.IValueProperty;
27
import org.eclipse.core.databinding.property.value.IValueProperty;
28
import org.eclipse.core.internal.databinding.property.ListPropertyDetailValuesList;
28
import org.eclipse.core.internal.databinding.property.ListPropertyDetailValuesList;
29
import org.eclipse.core.internal.databinding.property.list.ReducedListValueProperty;
29
30
30
/**
31
/**
31
 * Abstract implementation of IListProperty.
32
 * Abstract implementation of IListProperty.
Lines 139-142 Link Here
139
	public final IListProperty values(IValueProperty detailValue) {
140
	public final IListProperty values(IValueProperty detailValue) {
140
		return new ListPropertyDetailValuesList(this, detailValue);
141
		return new ListPropertyDetailValuesList(this, detailValue);
141
	}
142
	}
143
144
	/**
145
	 * @since 1.3
146
	 */
147
	public final IValueProperty reduce(IListReducer reducer) {
148
		return new ReducedListValueProperty(this, reducer);
149
	}
142
}
150
}
(-)src/org/eclipse/core/databinding/property/list/IListReducer.java (+49 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Matthew Hall 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
 *     Matthew Hall - initial API and implementation (bug 300043)
10
 ******************************************************************************/
11
12
package org.eclipse.core.databinding.property.list;
13
14
import java.util.List;
15
16
import org.eclipse.core.databinding.observable.list.IObservableList;
17
import org.eclipse.core.databinding.observable.value.IObservableValue;
18
19
/**
20
 * A list-to-value reducer. List reducers may be used to condense a list
21
 * property down to a value property.
22
 * 
23
 * @since 1.3
24
 * @noimplement This interface is not intended to be implemented by clients.
25
 * @see ListReducer
26
 */
27
public interface IListReducer {
28
29
	/**
30
	 * @param list
31
	 *            the list to reduce
32
	 * @return the list reduction
33
	 */
34
	public Object reduce(List list);
35
36
	/**
37
	 * @return the value type of the reduction
38
	 */
39
	public Object getValueType();
40
41
	/**
42
	 * @param master
43
	 *            the master list to reduce
44
	 * @return an IObservableValue observing the reduction of the specified
45
	 *         IObservableList
46
	 */
47
	public IObservableValue observeDetail(IObservableList master);
48
49
}
(-)src/org/eclipse/core/internal/databinding/property/value/ReducedListObservableValue.java (+41 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Matthew Hall 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
 *     Matthew Hall - initial API and implementation (bug 300043)
10
 ******************************************************************************/
11
12
package org.eclipse.core.internal.databinding.property.value;
13
14
import org.eclipse.core.databinding.observable.list.IObservableList;
15
import org.eclipse.core.databinding.observable.value.ComputedValue;
16
import org.eclipse.core.databinding.property.list.IListReducer;
17
18
/**
19
 * 
20
 */
21
public class ReducedListObservableValue extends ComputedValue {
22
23
	private IObservableList masterList;
24
	private IListReducer reducer;
25
26
	/**
27
	 * @param masterList
28
	 * @param reducer
29
	 */
30
	public ReducedListObservableValue(IObservableList masterList,
31
			IListReducer reducer) {
32
		super(masterList.getRealm(), reducer.getValueType());
33
		this.masterList = masterList;
34
		this.reducer = reducer;
35
	}
36
37
	protected Object calculate() {
38
		return reducer.reduce(masterList);
39
	}
40
41
}
(-)src/org/eclipse/core/internal/databinding/property/list/ReducedListValueProperty.java (+59 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Matthew Hall 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
 *     Matthew Hall - initial API and implementation (bug 300043)
10
 ******************************************************************************/
11
12
package org.eclipse.core.internal.databinding.property.list;
13
14
import org.eclipse.core.databinding.observable.ObservableTracker;
15
import org.eclipse.core.databinding.observable.Realm;
16
import org.eclipse.core.databinding.observable.list.IObservableList;
17
import org.eclipse.core.databinding.observable.value.IObservableValue;
18
import org.eclipse.core.databinding.property.list.IListReducer;
19
import org.eclipse.core.databinding.property.list.ListProperty;
20
import org.eclipse.core.databinding.property.value.ValueProperty;
21
import org.eclipse.core.internal.databinding.property.PropertyObservableUtil;
22
23
/**
24
 * 
25
 */
26
public class ReducedListValueProperty extends ValueProperty {
27
28
	private final ListProperty masterProperty;
29
	private final IListReducer reducer;
30
31
	/**
32
	 * @param masterProperty
33
	 * @param reducer
34
	 */
35
	public ReducedListValueProperty(ListProperty masterProperty,
36
			IListReducer reducer) {
37
		this.masterProperty = masterProperty;
38
		this.reducer = reducer;
39
	}
40
41
	public Object getValueType() {
42
		return reducer.getValueType();
43
	}
44
45
	public IObservableValue observe(Realm realm, Object source) {
46
		IObservableList masterList;
47
48
		ObservableTracker.setIgnore(true);
49
		try {
50
			masterList = masterProperty.observe(realm, source);
51
		} finally {
52
			ObservableTracker.setIgnore(false);
53
		}
54
55
		IObservableValue detailValue = reducer.observeDetail(masterList);
56
		PropertyObservableUtil.cascadeDispose(detailValue, masterList);
57
		return detailValue;
58
	}
59
}
(-)src/org/eclipse/core/databinding/property/list/ListReducer.java (+45 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Matthew Hall 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
 *     Matthew Hall - initial API and implementation (bug 300043)
10
 ******************************************************************************/
11
12
package org.eclipse.core.databinding.property.list;
13
14
import org.eclipse.core.databinding.observable.list.IObservableList;
15
import org.eclipse.core.databinding.observable.value.IObservableValue;
16
import org.eclipse.core.internal.databinding.property.value.ReducedListObservableValue;
17
18
/**
19
 * Abstract base implementation if {@link IListReducer}.
20
 * 
21
 * @since 1.3
22
 */
23
public abstract class ListReducer implements IListReducer {
24
25
	private final Object valueType;
26
27
	/**
28
	 * Constructs a list reducer with the given value type
29
	 * 
30
	 * @param valueType
31
	 *            the reduction value type
32
	 */
33
	protected ListReducer(Object valueType) {
34
		this.valueType = valueType;
35
	}
36
37
	public Object getValueType() {
38
		return valueType;
39
	}
40
41
	public IObservableValue observeDetail(IObservableList master) {
42
		return new ReducedListObservableValue(master, this);
43
	}
44
45
}
(-)src/org/eclipse/core/databinding/property/list/ListReducers.java (+44 lines)
Added Link Here
1
/*******************************************************************************
2
 * Copyright (c) 2010 Matthew Hall 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
 *     Matthew Hall - initial API and implementation (bug 300043)
10
 ******************************************************************************/
11
12
package org.eclipse.core.databinding.property.list;
13
14
import java.util.Iterator;
15
import java.util.List;
16
17
/**
18
 * Factory class for common list reducers
19
 * 
20
 * @since 1.3
21
 */
22
public class ListReducers {
23
	/**
24
	 * @param delimiter
25
	 *            the delimiter between each list element
26
	 * @return a String-valued list reducer which concatenates all elements in
27
	 *         the list, with the specified delimiter between each element.
28
	 */
29
	public static IListReducer join(final String delimiter) {
30
		return new ListReducer(String.class) {
31
			public Object reduce(List list) {
32
				StringBuffer buf = new StringBuffer();
33
34
				for (Iterator it = list.iterator(); it.hasNext();) {
35
					buf.append(it.next());
36
					if (it.hasNext())
37
						buf.append(delimiter);
38
				}
39
40
				return buf.toString();
41
			}
42
		};
43
	}
44
}

Return to bug 300043