Mercurial > projects > dwt2
comparison org.eclipse.core.databinding/src/org/eclipse/core/databinding/observable/map/CompositeMap.d @ 78:0a55d2d5a946
Added file for databinding
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Tue, 14 Apr 2009 11:35:29 +0200 |
parents | |
children | 383ce7bd736b |
comparison
equal
deleted
inserted
replaced
76:f05e6e8b2f2d | 78:0a55d2d5a946 |
---|---|
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 } |