]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.utils.datastructures/src/org/simantics/utils/datastructures/hints/HintContext.java
Utilize eclipse-collections immutable lists/map in datastructures
[simantics/platform.git] / bundles / org.simantics.utils.datastructures / src / org / simantics / utils / datastructures / hints / HintContext.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2015 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *     Semantum Oy - added getHintsUnsafe
12  *******************************************************************************/
13 /*
14  *
15  * @author Toni Kalajainen
16  */
17 package org.simantics.utils.datastructures.hints;
18
19 import java.util.ArrayList;
20 import java.util.Collection;
21 import java.util.HashMap;
22 import java.util.HashSet;
23 import java.util.List;
24 import java.util.Map;
25 import java.util.Map.Entry;
26 import java.util.Set;
27
28 import org.eclipse.collections.impl.factory.Maps;
29
30 /**
31  * 
32  * @author Toni Kalajainen
33  */
34 public class HintContext extends AbstractHintObservable implements IHintContext, Cloneable {
35
36     protected Map<Key, Object> hints = Maps.mutable.empty();
37
38     @Override
39     public void clearWithoutNotification() {
40         synchronized (this) {
41             hints.clear();
42         }
43     }
44
45     @Override
46     public synchronized boolean containsHint(Key key) {
47         return hints.get(key) != null;
48     }
49
50     @SuppressWarnings("unchecked")
51     @Override
52     public <E> E getHint(Key key) {
53         if (key == null)
54             throw new IllegalArgumentException("key is null");
55         synchronized (this) {
56             return (E) hints.get(key);
57         }
58     }
59
60     @SuppressWarnings("unchecked")
61     @Override
62     public <E> E removeHint(Key key) {
63         if (key == null)
64             throw new IllegalArgumentException("key is null");
65
66         Runnable notification;
67         Object oldValue = null;
68         synchronized(this) {
69             oldValue = hints.remove(key);
70             if (oldValue==null) return null;
71             notification = createFireKeyRemovedRunnable(this, key, oldValue);
72         }
73         notification.run();
74         return (E) oldValue;
75     }
76
77     /**
78      * Set a set of hints
79      * @param hints
80      */
81     public void removeHints(Collection<? extends Key> keys) {
82         List<Runnable> notifications = new ArrayList<Runnable>(hints.size());
83         synchronized (this) {
84             // Remove first
85             for (Key key : keys) {
86                 Object oldValue = this.hints.remove(key);
87                 if (oldValue == null)
88                     continue;
89                 Runnable notification = createFireKeyRemovedRunnable(this, key, oldValue);
90                 notifications.add( notification );
91             }
92         }
93
94         // Notify then
95         for (Runnable r : notifications)
96             r.run();
97     }
98
99     @Override
100     public void setHint(Key key, Object value) {
101         if (key == null)
102             throw new IllegalArgumentException("key is null");
103         if (value == null)
104             throw new IllegalArgumentException("value is null");
105         if (!key.isValueAccepted(value))
106             throw new RuntimeException("Value \""+value+"\" is not accepted with key "+key.getClass().getName());
107
108         Runnable notification;
109         synchronized(this) {
110             Object oldValue = hints.put(key, value);
111             notification = createFireKeyChangedRunnable(this, key, oldValue, value);
112         }
113         notification.run();
114     }
115
116     /**
117      * Set a set of hints
118      * @param hints
119      */
120     @Override
121     public void setHints(Map<Key, Object> hints) {
122         List<Runnable> notifications = new ArrayList<Runnable>(hints.size());
123         synchronized (this) {
124             // Insert first
125             for (Entry<Key, Object> e : hints.entrySet()) {
126                 Key key = e.getKey();
127                 Object value = e.getValue();
128                 if (value == null)
129                     throw new IllegalArgumentException("a value is null for key " + e.getKey());
130                 Object oldValue = this.hints.put(key, value);
131
132                 Runnable notification = createFireKeyChangedRunnable(this, key,
133                         oldValue, value);
134                 notifications.add( notification );
135             }
136         }
137
138         // Notify then
139         for (Runnable r : notifications)
140             r.run();
141     }
142
143     public Object setHintWithoutNotification(Key key, Object value) {
144         if (key == null)
145             throw new IllegalArgumentException("key is null");
146         if (value == null)
147             throw new IllegalArgumentException("value is null");
148         if (!key.isValueAccepted(value))
149             throw new RuntimeException("Value \""+value+"\" is not accepted with key "+key.getClass().getName());
150
151         synchronized(this) {
152             return hints.put(key, value);
153         }
154     }
155
156     /**
157      * Removes the specified hint without sending notifications about changes.
158      * 
159      * @param <E>
160      * @param key
161      * @return removed hint value
162      */
163     @SuppressWarnings("unchecked")
164     public <E> E removeHintWithoutNotification(Key key) {
165         if (key == null)
166             throw new IllegalArgumentException("key is null");
167
168         Object oldValue = null;
169         synchronized(this) {
170             oldValue = hints.remove(key);
171         }
172         return (E) oldValue;
173     }
174
175     /**
176      * Replace the current hints with the specified set of hints. Hints
177      * that were previously included but are not contained by the new map will
178      * not be preserved by this operation.
179      * 
180      * @param hints the new hints to set
181      */
182     public void replaceHintsWithoutNotification(Map<Key, Object> hints) {
183         Map<Key, Object> copy = new HashMap<Key, Object>(hints);
184         synchronized (this) {
185             this.hints = copy;
186         }
187     }
188
189     /**
190      * Replace the current set of hints with the new specified set of hints.
191      * Notifications are sent for removed and changed hints.
192      * 
193      * @param hints
194      */
195     public void replaceHints(Map<Key, Object> newHints) {
196         List<Runnable> notifications = new ArrayList<Runnable>(Math.max(this.hints.size(), newHints.size()));
197         synchronized (this) {
198             // Calculate removed keys
199             Set<Key> removedKeys = new HashSet<Key>(this.hints.keySet());
200             removedKeys.removeAll(newHints.keySet());
201
202             // Remove keys
203             for (Key key : removedKeys) {
204                 Object oldValue = this.hints.remove(key);
205                 if (oldValue == null)
206                     continue;
207
208                 Runnable notification = createFireKeyRemovedRunnable(this, key, oldValue);
209                 notifications.add( notification );
210             }
211
212             // Replace/set existing/new hints
213             for (Entry<Key, Object> e : newHints.entrySet()) {
214                 Key key = e.getKey();
215                 Object value = e.getValue();
216                 if (value == null)
217                     throw new IllegalArgumentException("a value is null for key " + e.getKey());
218                 Object oldValue = this.hints.put(key, value);
219                 if (value.equals(oldValue))
220                     continue;
221
222                 Runnable notification = createFireKeyChangedRunnable(this, key,
223                         oldValue, value);
224                 notifications.add( notification );
225             }
226         }
227
228         // Notify then
229         for (Runnable r : notifications)
230             r.run();
231     }
232
233     /**
234      * Set a set of hints without notifying any generic hint or key-specific
235      * listeners. This method will only replace the possible previous values of
236      * the hints included in the specified map. Hints not included in the map
237      * will be preserved as such.
238      * 
239      * @param hints the new hints to set
240      */
241     public void setHintsWithoutNotification(Map<Key, Object> hints) {
242         synchronized (this) {
243             for (Entry<Key, Object> e : hints.entrySet()) {
244                 Key key = e.getKey();
245                 Object value = e.getValue();
246                 if (value == null)
247                     throw new IllegalArgumentException("a value is null for key " + e.getKey());
248                 this.hints.put(key, value);
249             }
250         }
251     }
252
253     /**
254      * Compares two object for equality.
255      * <p>
256      * Some times it is annoying to compare two objects if their
257      * value may be null.
258      * 
259      * @param o1 obj1
260      * @param o2 obj2
261      * @return true if equal or both null
262      */
263     public static boolean objectEquals(Object o1, Object o2) {
264         if (o1==o2) return true;
265         if (o1==null && o2==null) return true;
266         if (o1==null || o2==null) return false;
267         return o1.equals(o2);
268     }
269
270     @Override
271     public synchronized Map<Key, Object> getHints() {
272         return new HashMap<Key, Object>(hints);
273     }
274
275     @Override
276     public Map<Key, Object> getHintsUnsafe() {
277         return hints;
278     }
279
280     @SuppressWarnings("unchecked")
281     @Override
282     public synchronized <E extends Key> Map<E, Object> getHintsOfClass(Class<E> clazz) {
283         Map<E, Object> result = new HashMap<E, Object>();
284         for (Entry<Key, Object> e : hints.entrySet()) {
285             Key key = e.getKey();
286             if (clazz.isAssignableFrom(key.getClass()))
287                 result.put((E)key, e.getValue());
288         }
289         return result;
290     }
291
292     public synchronized int size()
293     {
294         return hints.size();
295     }
296
297     @Override
298     public Object clone() {
299         try {
300             return super.clone();
301         } catch (CloneNotSupportedException e) {
302             throw new Error(e);
303         }
304     }
305
306 }