78
|
1 /*******************************************************************************
|
|
2 * Copyright (c) 2006, 2008 IBM Corporation 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 * IBM Corporation - initial API and implementation
|
|
10 *******************************************************************************/
|
|
11 module org.eclipse.core.databinding.observable.map.CompositeMap;
|
|
12
|
|
13 import java.lang.all;
|
|
14
|
|
15 import java.util.Collections;
|
|
16 import java.util.HashMap;
|
|
17 import java.util.HashSet;
|
|
18 import java.util.Iterator;
|
|
19 import java.util.Map;
|
|
20 import java.util.Set;
|
|
21
|
|
22 import org.eclipse.core.databinding.observable.Diffs;
|
|
23 import org.eclipse.core.databinding.observable.Realm;
|
|
24 import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
|
|
25 import org.eclipse.core.databinding.observable.set.WritableSet;
|
|
26 import org.eclipse.core.runtime.Assert;
|
|
27
|
|
28 /**
|
|
29 * A read-only observable map formed by the composition of two observable maps.
|
|
30 * If map1 maps keys a:A to values b1:B, and map2 maps keys b2:B to values c:C,
|
|
31 * the composite map maps keys a:A to values c:C. For example, map1 could map
|
|
32 * Order objects to their corresponding Customer objects, and map2 could map
|
|
33 * Customer objects to their "last name" property of type String. The composite
|
|
34 * map of map1 and map2 would then map Order objects to their customers' last
|
|
35 * names.
|
|
36 *
|
|
37 * <p>
|
|
38 * This class is thread safe. All state accessing methods must be invoked from
|
|
39 * the {@link Realm#isCurrent() current realm}. Methods for adding and removing
|
|
40 * listeners may be invoked from any thread.
|
|
41 * </p>
|
|
42 *
|
|
43 * @since 1.1
|
|
44 *
|
|
45 */
|
|
46 public class CompositeMap : ObservableMap {
|
|
47
|
|
48 private Map valueToElements = new HashMap();
|
|
49
|
|
50 // adds that need to go through the second map and thus will be picked up by
|
|
51 // secondMapListener.
|
|
52 private Set pendingAdds = new HashSet();
|
|
53
|
|
54 // Removes that need to go through the second map and thus will be picked up
|
|
55 // by
|
|
56 // secondMapListener. Maps from value being removed to key being removed.
|
|
57 private Map pendingRemoves = new HashMap();
|
|
58
|
|
59 // Changes that need to go through the second map and thus will be picked up
|
|
60 // by
|
|
61 // secondMapListener. Maps from old value to new value and new value to old
|
|
62 // value.
|
|
63 private Map pendingChanges = new HashMap();
|
|
64
|
|
65 private IMapChangeListener firstMapListener = new class() IMapChangeListener {
|
|
66
|
|
67 public void handleMapChange(MapChangeEvent event) {
|
|
68 MapDiff diff = event.diff;
|
|
69 Set rangeSetAdditions = new HashSet();
|
|
70 Set rangeSetRemovals = new HashSet();
|
|
71 final Set adds = new HashSet();
|
|
72 final Set changes = new HashSet();
|
|
73 final Set removes = new HashSet();
|
|
74 final Map oldValues = new HashMap();
|
|
75
|
|
76 for (Iterator it = diff.getAddedKeys().iterator(); it.hasNext();) {
|
|
77 Object addedKey = it.next();
|
|
78 Object newValue = diff.getNewValue(addedKey);
|
|
79 addMapping(addedKey, newValue);
|
|
80 if (!rangeSet.contains(newValue)) {
|
|
81 pendingAdds.add(newValue);
|
|
82 rangeSetAdditions.add(newValue);
|
|
83 } else {
|
|
84 adds.add(addedKey);
|
|
85 wrappedMap.put(addedKey, secondMap.get(newValue));
|
|
86 }
|
|
87 }
|
|
88 for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) {
|
|
89 Object changedKey = it.next();
|
|
90 Object oldValue = diff.getOldValue(changedKey);
|
|
91 Object newValue = diff.getNewValue(changedKey);
|
|
92 bool removed = removeMapping(changedKey, oldValue);
|
|
93 addMapping(changedKey, newValue);
|
|
94 bool added = !rangeSet.contains(newValue);
|
|
95 if (removed) {
|
|
96 pendingRemoves.put(oldValue, changedKey);
|
|
97 rangeSetRemovals.add(oldValue);
|
|
98 }
|
|
99 if (added) {
|
|
100 pendingAdds.add(newValue);
|
|
101 rangeSetAdditions.add(newValue);
|
|
102 }
|
|
103 if (added || removed) {
|
|
104 pendingChanges.put(oldValue, newValue);
|
|
105 pendingChanges.put(newValue, oldValue);
|
|
106 } else {
|
|
107 changes.add(changedKey);
|
|
108 oldValues.put(changedKey, oldValue);
|
|
109 wrappedMap.put(changedKey, secondMap.get(newValue));
|
|
110 }
|
|
111 }
|
|
112 for (Iterator it = diff.getRemovedKeys().iterator(); it.hasNext();) {
|
|
113 Object removedKey = it.next();
|
|
114 Object oldValue = diff.getOldValue(removedKey);
|
|
115 if (removeMapping(removedKey, oldValue)) {
|
|
116 pendingRemoves.put(oldValue, removedKey);
|
|
117 rangeSetRemovals.add(oldValue);
|
|
118 } else {
|
|
119 removes.add(removedKey);
|
|
120 oldValues.put(removedKey, secondMap.get(oldValue));
|
|
121 wrappedMap.remove(removedKey);
|
|
122 }
|
|
123 }
|
|
124
|
|
125 if (adds.size() > 0 || removes.size() > 0 || changes.size() > 0) {
|
|
126 fireMapChange(new class() MapDiff {
|
|
127
|
|
128 public Set getAddedKeys() {
|
|
129 return adds;
|
|
130 }
|
|
131
|
|
132 public Set getChangedKeys() {
|
|
133 return changes;
|
|
134 }
|
|
135
|
|
136 public Object getNewValue(Object key) {
|
|
137 return wrappedMap.get(key);
|
|
138 }
|
|
139
|
|
140 public Object getOldValue(Object key) {
|
|
141 return oldValues.get(key);
|
|
142 }
|
|
143
|
|
144 public Set getRemovedKeys() {
|
|
145 return removes;
|
|
146 }
|
|
147 });
|
|
148 }
|
|
149
|
|
150 if (rangeSetAdditions.size() > 0 || rangeSetRemovals.size() > 0) {
|
|
151 rangeSet.addAndRemove(rangeSetAdditions, rangeSetRemovals);
|
|
152 }
|
|
153 }
|
|
154 };
|
|
155
|
|
156 private IMapChangeListener secondMapListener = new class() IMapChangeListener {
|
|
157
|
|
158 public void handleMapChange(MapChangeEvent event) {
|
|
159 MapDiff diff = event.diff;
|
|
160 final Set adds = new HashSet();
|
|
161 final Set changes = new HashSet();
|
|
162 final Set removes = new HashSet();
|
|
163 final Map oldValues = new HashMap();
|
|
164 final Map newValues = new HashMap();
|
|
165 Set addedKeys = new HashSet(diff.getAddedKeys());
|
|
166 Set removedKeys = new HashSet(diff.getRemovedKeys());
|
|
167
|
|
168 for (Iterator it = addedKeys.iterator(); it.hasNext();) {
|
|
169 Object addedKey = it.next();
|
|
170 Set elements = getElementsForValue(addedKey);
|
|
171 Object newValue = diff.getNewValue(addedKey);
|
|
172 if (pendingChanges.containsKey(addedKey)) {
|
|
173 Object oldKey = pendingChanges.remove(addedKey);
|
|
174 Object oldValue;
|
|
175 if (removedKeys.remove(oldKey)) {
|
|
176 oldValue = diff.getOldValue(oldKey);
|
|
177 } else {
|
|
178 oldValue = secondMap.get(oldKey);
|
|
179 }
|
|
180 pendingChanges.remove(oldKey);
|
|
181 pendingAdds.remove(addedKey);
|
|
182 pendingRemoves.remove(oldKey);
|
|
183 for (Iterator it2 = elements.iterator(); it2.hasNext();) {
|
|
184 Object element = it2.next();
|
|
185 changes.add(element);
|
|
186 oldValues.put(element, oldValue);
|
|
187 newValues.put(element, newValue);
|
|
188 wrappedMap.put(element, newValue);
|
|
189 }
|
|
190 } else if (pendingAdds.remove(addedKey)) {
|
|
191 for (Iterator it2 = elements.iterator(); it2.hasNext();) {
|
|
192 Object element = it2.next();
|
|
193 adds.add(element);
|
|
194 newValues.put(element, newValue);
|
|
195 wrappedMap.put(element, newValue);
|
|
196 }
|
|
197 } else {
|
|
198 Assert.isTrue(false, "unexpected case"); //$NON-NLS-1$
|
|
199 }
|
|
200 }
|
|
201 for (Iterator it = diff.getChangedKeys().iterator(); it.hasNext();) {
|
|
202 Object changedKey = it.next();
|
|
203 Set elements = getElementsForValue(changedKey);
|
|
204 for (Iterator it2 = elements.iterator(); it2.hasNext();) {
|
|
205 Object element = it2.next();
|
|
206 changes.add(element);
|
|
207 oldValues.put(element, diff.getOldValue(changedKey));
|
|
208 Object newValue = diff.getNewValue(changedKey);
|
|
209 newValues.put(element, newValue);
|
|
210 wrappedMap.put(element, newValue);
|
|
211 }
|
|
212 }
|
|
213 for (Iterator it = removedKeys.iterator(); it.hasNext();) {
|
|
214 Object removedKey = it.next();
|
|
215 Object element = pendingRemoves.remove(removedKey);
|
|
216 if (element !is null) {
|
|
217 if (pendingChanges.containsKey(removedKey)) {
|
|
218 Object newKey = pendingChanges.remove(removedKey);
|
|
219 pendingChanges.remove(newKey);
|
|
220 pendingAdds.remove(newKey);
|
|
221 pendingRemoves.remove(removedKey);
|
|
222 changes.add(element);
|
|
223 oldValues.put(element, diff.getOldValue(removedKey));
|
|
224 Object newValue = secondMap.get(newKey);
|
|
225 newValues.put(element, newValue);
|
|
226 wrappedMap.put(element, newValue);
|
|
227 } else {
|
|
228 removes.add(element);
|
|
229 Object oldValue = diff.getOldValue(removedKey);
|
|
230 oldValues.put(element, oldValue);
|
|
231 wrappedMap.remove(element);
|
|
232 }
|
|
233 } else {
|
|
234 Assert.isTrue(false, "unexpected case"); //$NON-NLS-1$
|
|
235 }
|
|
236 }
|
|
237
|
|
238 if (adds.size() > 0 || removes.size() > 0 || changes.size() > 0) {
|
|
239 fireMapChange(new class() MapDiff {
|
|
240
|
|
241 public Set getAddedKeys() {
|
|
242 return adds;
|
|
243 }
|
|
244
|
|
245 public Set getChangedKeys() {
|
|
246 return changes;
|
|
247 }
|
|
248
|
|
249 public Object getNewValue(Object key) {
|
|
250 return newValues.get(key);
|
|
251 }
|
|
252
|
|
253 public Object getOldValue(Object key) {
|
|
254 return oldValues.get(key);
|
|
255 }
|
|
256
|
|
257 public Set getRemovedKeys() {
|
|
258 return removes;
|
|
259 }
|
|
260 });
|
|
261 }
|
|
262 }
|
|
263 };
|
|
264
|
|
265 private IObservableMap firstMap;
|
|
266 private IObservableMap secondMap;
|
|
267
|
|
268 private static class WritableSetPlus : WritableSet {
|
|
269 void addAndRemove(Set additions, Set removals) {
|
|
270 wrappedSet.removeAll(removals);
|
|
271 wrappedSet.addAll(additions);
|
|
272 fireSetChange(Diffs.createSetDiff(additions, removals));
|
|
273 }
|
|
274 }
|
|
275
|
|
276 private WritableSetPlus rangeSet = new WritableSetPlus();
|
|
277
|
|
278 /**
|
|
279 * Creates a new composite map. Because the key set of the second map is
|
|
280 * determined by the value set of the given observable map
|
|
281 * <code>firstMap</code>, it cannot be passed in as an argument. Instead,
|
|
282 * the second map will be created by calling
|
|
283 * <code>secondMapFactory.createObservable(valueSet())</code>.
|
|
284 *
|
|
285 * @param firstMap
|
|
286 * the first map
|
|
287 * @param secondMapFactory
|
|
288 * a factory that creates the second map when given an observable
|
|
289 * set representing the value set of <code>firstMap</code>.
|
|
290 */
|
|
291 public this(IObservableMap firstMap,
|
|
292 IObservableFactory secondMapFactory) {
|
|
293 super(firstMap.getRealm(), new HashMap());
|
|
294 this.firstMap = firstMap;
|
|
295 firstMap.addMapChangeListener(firstMapListener);
|
|
296 for (Iterator it = firstMap.entrySet().iterator(); it.hasNext();) {
|
|
297 Map.Entry entry = cast(Entry) it.next();
|
|
298 addMapping(entry.getKey(), entry.getValue());
|
|
299 rangeSet.add(entry.getValue());
|
|
300 }
|
|
301 this.secondMap = cast(IObservableMap) secondMapFactory
|
|
302 .createObservable(rangeSet);
|
|
303 secondMap.addMapChangeListener(secondMapListener);
|
|
304 for (Iterator it = firstMap.entrySet().iterator(); it.hasNext();) {
|
|
305 Map.Entry entry = cast(Entry) it.next();
|
|
306 wrappedMap.put(entry.getKey(), secondMap.get(entry.getValue()));
|
|
307 }
|
|
308 }
|
|
309
|
|
310 /**
|
|
311 * @param key
|
|
312 * @param value
|
|
313 */
|
|
314 private void addMapping(Object key, Object value) {
|
|
315 Object elementOrSet = valueToElements.get(value);
|
|
316 if (elementOrSet is null) {
|
|
317 valueToElements.put(value, key);
|
|
318 return;
|
|
319 }
|
|
320 if (!( null !is cast(Set)elementOrSet )) {
|
|
321 elementOrSet = new HashSet(Collections.singleton(elementOrSet));
|
|
322 valueToElements.put(value, elementOrSet);
|
|
323 }
|
|
324 Set set = cast(Set) elementOrSet;
|
|
325 set.add(key);
|
|
326 }
|
|
327
|
|
328 /**
|
|
329 * @param key
|
|
330 * @param value
|
|
331 */
|
|
332 private bool removeMapping(Object key, Object value) {
|
|
333 Object elementOrSet = valueToElements.get(value);
|
|
334 if ( null !is cast(Set)elementOrSet ) {
|
|
335 Set set = cast(Set) elementOrSet;
|
|
336 set.remove(key);
|
|
337 if (set.size() is 0) {
|
|
338 valueToElements.remove(value);
|
|
339 return true;
|
|
340 }
|
|
341 return false;
|
|
342 }
|
|
343 valueToElements.remove(value);
|
|
344 return true;
|
|
345 }
|
|
346
|
|
347 private Set getElementsForValue(Object value) {
|
|
348 Object elementOrSet = valueToElements.get(value);
|
|
349 if ( null !is cast(Set)elementOrSet ) {
|
|
350 return cast(Set) elementOrSet;
|
|
351 }
|
|
352 return elementOrSet is null ? Collections.EMPTY_SET : Collections
|
|
353 .singleton(elementOrSet);
|
|
354 }
|
|
355
|
|
356 public synchronized void dispose() {
|
|
357 super.dispose();
|
|
358 firstMap.removeMapChangeListener(firstMapListener);
|
|
359 firstMap = null;
|
|
360 secondMap = null;
|
|
361 }
|
|
362
|
|
363 }
|