Skip to main content

ferroui_base/collections/
ferro_dictionary.rs

1use super::ferro_list::raise_collection_changed;
2use super::{CollectionChangedHandler, NotifyCollectionChangedAction, NotifyCollectionChangedEventArgs};
3use crate::data::core::INDEXER_NAME;
4use crate::data::model::{CollectionChange, Event, INotifyCollectionChanged, INotifyPropertyChanged};
5use crate::utilities::{HandlerList, WeakEventSender};
6use std::cell::RefCell;
7use std::collections::HashMap;
8use std::fmt::Display;
9use std::hash::Hash;
10use std::rc::{Rc, Weak};
11
12/// The storage of a [`FerroDictionary`]: a hash index over entry slots.
13///
14/// Enumeration follows the slots, and a removed slot is reused by the next
15/// addition (most recently freed first), which gives the enumeration order of
16/// the dictionary this mirrors: insertion order as long as nothing is
17/// removed.
18struct Inner<K, V> {
19    entries: Vec<Option<(K, V)>>,
20    free: Vec<usize>,
21    map: HashMap<K, usize>,
22}
23
24impl<K: Eq + Hash + Clone, V: Clone> Inner<K, V> {
25    fn with_capacity(capacity: usize) -> Self {
26        Self { entries: Vec::with_capacity(capacity), free: Vec::new(), map: HashMap::with_capacity(capacity) }
27    }
28
29    fn get(&self, key: &K) -> Option<&V> {
30        self.map.get(key).and_then(|&slot| self.entries[slot].as_ref()).map(|(_, v)| v)
31    }
32
33    /// Inserts or replaces; returns the replaced value.
34    fn insert(&mut self, key: K, value: V) -> Option<V> {
35        if let Some(&slot) = self.map.get(&key) {
36            return self.entries[slot].replace((key, value)).map(|(_, old)| old);
37        }
38        let slot = match self.free.pop() {
39            Some(slot) => {
40                self.entries[slot] = Some((key.clone(), value));
41                slot
42            }
43            None => {
44                self.entries.push(Some((key.clone(), value)));
45                self.entries.len() - 1
46            }
47        };
48        self.map.insert(key, slot);
49        None
50    }
51
52    fn remove(&mut self, key: &K) -> Option<V> {
53        let slot = self.map.remove(key)?;
54        self.free.push(slot);
55        self.entries[slot].take().map(|(_, v)| v)
56    }
57
58    fn to_vec(&self) -> Vec<(K, V)> {
59        self.entries.iter().flatten().cloned().collect()
60    }
61}
62
63/// A notifying dictionary.
64///
65/// Changes raise the collection changed event and property changed
66/// notifications for `Count` and `Item[key]` (`Item` when the dictionary is
67/// cleared). Keys are shown in those names through their [`Display`] form.
68///
69/// Enumeration works on a copy, so the dictionary may be modified from
70/// within an enumeration or a change notification.
71///
72/// The dictionary is a reference object: the value is a shared handle,
73/// clones refer to the same dictionary and handles compare by identity.
74pub struct FerroDictionary<K, V>(Rc<FerroDictionaryData<K, V>>);
75
76struct FerroDictionaryData<K, V> {
77    inner: RefCell<Inner<K, V>>,
78    collection_changed: HandlerList<CollectionChangedHandler<(K, V)>>,
79    /// The untyped form of the collection changed event
80    /// ([`INotifyCollectionChanged`]). Its handlers share their tokens with
81    /// the typed ones, so that one change runs all of them in subscription
82    /// order, as the single event of the original.
83    untyped_collection_changed: Event<CollectionChange>,
84    property_changed: Event<str>,
85}
86
87/// The weak form of a [`FerroDictionary`] handle.
88pub struct WeakFerroDictionary<K, V>(Weak<FerroDictionaryData<K, V>>);
89
90impl<K, V> Clone for WeakFerroDictionary<K, V> {
91    fn clone(&self) -> Self {
92        Self(self.0.clone())
93    }
94}
95
96impl<K: 'static, V: 'static> WeakEventSender for FerroDictionary<K, V> {
97    type Weak = WeakFerroDictionary<K, V>;
98
99    fn downgrade_sender(&self) -> WeakFerroDictionary<K, V> {
100        WeakFerroDictionary(Rc::downgrade(&self.0))
101    }
102
103    fn upgrade_sender(weak: &WeakFerroDictionary<K, V>) -> Option<Self> {
104        weak.0.upgrade().map(FerroDictionary)
105    }
106
107    fn sender_address(&self) -> usize {
108        Rc::as_ptr(&self.0) as *const () as usize
109    }
110}
111
112impl<K, V> Clone for FerroDictionary<K, V> {
113    #[inline]
114    fn clone(&self) -> Self {
115        Self(self.0.clone())
116    }
117}
118
119impl<K, V> PartialEq for FerroDictionary<K, V> {
120    #[inline]
121    fn eq(&self, other: &Self) -> bool {
122        self.ptr_eq(other)
123    }
124}
125
126impl<K, V> FerroDictionary<K, V> {
127    /// Whether two handles refer to the same dictionary.
128    #[inline]
129    pub fn ptr_eq(&self, other: &Self) -> bool {
130        Rc::ptr_eq(&self.0, &other.0)
131    }
132}
133
134impl<K, V> std::fmt::Debug for FerroDictionary<K, V> {
135    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
136        f.write_str("FerroDictionary")
137    }
138}
139
140impl<K: Eq + Hash + Clone + Display, V: Clone> Default for FerroDictionary<K, V> {
141    fn default() -> Self {
142        Self::new()
143    }
144}
145
146impl<K: Eq + Hash + Clone + Display, V: Clone> FerroDictionary<K, V> {
147    /// Creates an empty dictionary.
148    pub fn new() -> Self {
149        Self::with_capacity(0)
150    }
151
152    /// Creates an empty dictionary with room for `capacity` entries.
153    pub fn with_capacity(capacity: usize) -> Self {
154        Self(Rc::new(FerroDictionaryData {
155            inner: RefCell::new(Inner::with_capacity(capacity)),
156            collection_changed: HandlerList::with_ordered_ids(),
157            untyped_collection_changed: Event::with_ordered_ids(),
158            property_changed: Event::new(),
159        }))
160    }
161
162    /// Creates a dictionary with the entries of `dictionary`. A key that
163    /// occurs more than once keeps its last value.
164    pub fn from_dictionary(dictionary: impl IntoIterator<Item = (K, V)>) -> Self {
165        let this = Self::new();
166        {
167            let mut inner = this.0.inner.borrow_mut();
168            for (key, value) in dictionary {
169                inner.insert(key, value);
170            }
171        }
172        this
173    }
174
175    /// Subscribes to changes of the collection. Returns a token for
176    /// [`remove_collection_changed`](Self::remove_collection_changed).
177    pub fn add_collection_changed(&self, handler: Rc<CollectionChangedHandler<(K, V)>>) -> u64 {
178        self.0.collection_changed.add(handler)
179    }
180
181    /// Unsubscribes a handler.
182    pub fn remove_collection_changed(&self, token: u64) -> bool {
183        self.0.collection_changed.remove(token)
184    }
185
186    /// Whether anything is subscribed to changes of the collection, typed or
187    /// untyped.
188    pub fn has_collection_changed_subscribers(&self) -> bool {
189        !self.0.collection_changed.is_empty() || self.0.untyped_collection_changed.has_handlers()
190    }
191
192    /// The number of entries.
193    pub fn count(&self) -> usize {
194        self.0.inner.borrow().map.len()
195    }
196
197    pub fn is_empty(&self) -> bool {
198        self.count() == 0
199    }
200
201    pub fn is_read_only(&self) -> bool {
202        false
203    }
204
205    /// The keys, in enumeration order.
206    pub fn keys(&self) -> Vec<K> {
207        self.0.inner.borrow().entries.iter().flatten().map(|(k, _)| k.clone()).collect()
208    }
209
210    /// The values, in enumeration order.
211    pub fn values(&self) -> Vec<V> {
212        self.0.inner.borrow().entries.iter().flatten().map(|(_, v)| v.clone()).collect()
213    }
214
215    /// The value for `key`. Panics if the key is not present.
216    pub fn get(&self, key: &K) -> V {
217        match self.try_get_value(key) {
218            Some(value) => value,
219            None => panic!("The given key '{key}' was not present in the dictionary."),
220        }
221    }
222
223    /// The value for `key`, if present.
224    pub fn try_get_value(&self, key: &K) -> Option<V> {
225        self.0.inner.borrow().get(key).cloned()
226    }
227
228    /// Sets the value for `key`, adding the entry if it is not present.
229    pub fn set(&self, key: K, value: V) {
230        let old = self.0.inner.borrow_mut().insert(key.clone(), value.clone());
231        match old {
232            Some(old) => {
233                self.0.property_changed.raise(&format!("{INDEXER_NAME}[{key}]"));
234                if self.has_collection_changed_subscribers() {
235                    self.raise_collection_changed(
236                        NotifyCollectionChangedAction::Replace,
237                        &[(key.clone(), value)],
238                        &[(key, old)],
239                    );
240                }
241            }
242            None => self.notify_add(key, value),
243        }
244    }
245
246    /// Adds an entry. Panics if the key is already present.
247    pub fn add(&self, key: K, value: V) {
248        {
249            let mut inner = self.0.inner.borrow_mut();
250            if inner.map.contains_key(&key) {
251                drop(inner);
252                panic!("An item with the same key has already been added. Key: {key}");
253            }
254            inner.insert(key.clone(), value.clone());
255        }
256        self.notify_add(key, value);
257    }
258
259    /// Removes all entries.
260    pub fn clear(&self) {
261        let old = std::mem::replace(&mut *self.0.inner.borrow_mut(), Inner::with_capacity(0));
262
263        self.0.property_changed.raise("Count");
264        self.0.property_changed.raise(INDEXER_NAME);
265
266        if self.has_collection_changed_subscribers() {
267            self.raise_collection_changed(NotifyCollectionChangedAction::Remove, &[], &old.to_vec());
268        }
269    }
270
271    pub fn contains_key(&self, key: &K) -> bool {
272        self.0.inner.borrow().map.contains_key(key)
273    }
274
275    /// Whether the dictionary has the entry `key` with a value equal to
276    /// `value`.
277    pub fn contains(&self, key: &K, value: &V) -> bool
278    where
279        V: PartialEq,
280    {
281        self.0.inner.borrow().get(key).is_some_and(|v| v == value)
282    }
283
284    /// The entries, in enumeration order.
285    pub fn to_vec(&self) -> Vec<(K, V)> {
286        self.0.inner.borrow().to_vec()
287    }
288
289    /// Copies the entries, in enumeration order, to `array`, starting at
290    /// `array_index` (`CopyTo`).
291    ///
292    /// # Panics
293    /// Panics if `array` is too small, as the managed original throws.
294    pub fn copy_to(&self, array: &mut [(K, V)], array_index: usize) {
295        let entries = self.to_vec();
296        assert!(
297            array_index <= array.len() && array.len() - array_index >= entries.len(),
298            "Destination array is not long enough to copy all the items in the collection. Check array index and length."
299        );
300        array[array_index..array_index + entries.len()].clone_from_slice(&entries);
301    }
302
303    /// Calls `f` for each entry of a copy of the dictionary.
304    pub fn for_each(&self, mut f: impl FnMut(&K, &V)) {
305        for (key, value) in self.to_vec() {
306            f(&key, &value);
307        }
308    }
309
310    /// Removes the entry for `key`. Returns false if it is not present.
311    pub fn remove(&self, key: &K) -> bool {
312        let removed = self.0.inner.borrow_mut().remove(key);
313        match removed {
314            Some(value) => {
315                self.0.property_changed.raise("Count");
316                self.0.property_changed.raise(&format!("{INDEXER_NAME}[{key}]"));
317
318                if self.has_collection_changed_subscribers() {
319                    self.raise_collection_changed(NotifyCollectionChangedAction::Remove, &[], &[(key.clone(), value)]);
320                }
321                true
322            }
323            None => false,
324        }
325    }
326
327    fn notify_add(&self, key: K, value: V) {
328        self.0.property_changed.raise("Count");
329        self.0.property_changed.raise(&format!("{INDEXER_NAME}[{key}]"));
330
331        if self.has_collection_changed_subscribers() {
332            self.raise_collection_changed(NotifyCollectionChangedAction::Add, &[(key, value)], &[]);
333        }
334    }
335
336    fn raise_collection_changed(&self, action: NotifyCollectionChangedAction, new_items: &[(K, V)], old_items: &[(K, V)]) {
337        // Entries of a dictionary have no position.
338        let e = NotifyCollectionChangedEventArgs { action, new_items, old_items, new_starting_index: -1, old_starting_index: -1 };
339        raise_collection_changed(&self.0.collection_changed, &self.0.untyped_collection_changed, &e);
340    }
341}
342
343impl<K, V> INotifyCollectionChanged for FerroDictionary<K, V> {
344    fn collection_changed(&self) -> &Event<CollectionChange> {
345        &self.0.untyped_collection_changed
346    }
347}
348
349impl<K: Eq + Hash + Clone + Display, V: Clone> INotifyPropertyChanged for FerroDictionary<K, V> {
350    fn property_changed(&self) -> &Event<str> {
351        &self.0.property_changed
352    }
353}
354
355#[cfg(test)]
356mod tests {
357    use super::*;
358    use std::cell::RefCell;
359
360    type Args = (NotifyCollectionChangedAction, Vec<(String, String)>, Vec<(String, String)>, i32, i32);
361
362    struct CollectionChangedTracker {
363        args: Rc<RefCell<Option<Args>>>,
364    }
365
366    impl CollectionChangedTracker {
367        fn new(target: &FerroDictionary<String, String>) -> Self {
368            let args = Rc::new(RefCell::new(None));
369            let sink = args.clone();
370            target.add_collection_changed(Rc::new(move |e: &NotifyCollectionChangedEventArgs<'_, (String, String)>| {
371                *sink.borrow_mut() =
372                    Some((e.action, e.new_items.to_vec(), e.old_items.to_vec(), e.new_starting_index, e.old_starting_index));
373            }));
374            Self { args }
375        }
376
377        fn args(&self) -> Args {
378            self.args.borrow().clone().expect("a collection changed notification")
379        }
380    }
381
382    struct PropertyChangedTracker {
383        names: Rc<RefCell<Vec<String>>>,
384    }
385
386    impl PropertyChangedTracker {
387        fn new(target: &FerroDictionary<String, String>) -> Self {
388            let names = Rc::new(RefCell::new(Vec::new()));
389            let sink = names.clone();
390            target.property_changed().add(Rc::new(move |name: &str| sink.borrow_mut().push(name.to_string())));
391            Self { names }
392        }
393
394        fn names(&self) -> Vec<String> {
395            self.names.borrow().clone()
396        }
397    }
398
399    fn s(value: &str) -> String {
400        value.to_string()
401    }
402
403    fn pair(key: &str, value: &str) -> (String, String) {
404        (s(key), s(value))
405    }
406
407    #[test]
408    fn adding_item_should_raise_collection_changed() {
409        let target = FerroDictionary::<String, String>::new();
410        let tracker = CollectionChangedTracker::new(&target);
411
412        target.add(s("foo"), s("bar"));
413
414        let (action, new_items, _, new_starting_index, _) = tracker.args();
415        assert_eq!(NotifyCollectionChangedAction::Add, action);
416        assert_eq!(-1, new_starting_index);
417        assert_eq!(1, new_items.len());
418        assert_eq!(pair("foo", "bar"), new_items[0]);
419    }
420
421    #[test]
422    fn adding_item_should_raise_property_changed() {
423        let target = FerroDictionary::<String, String>::new();
424        let tracker = PropertyChangedTracker::new(&target);
425
426        target.add(s("foo"), s("bar"));
427
428        assert_eq!(vec!["Count", "Item[foo]"], tracker.names());
429    }
430
431    #[test]
432    fn assigning_item_should_raise_collection_changed_add() {
433        let target = FerroDictionary::<String, String>::new();
434        let tracker = CollectionChangedTracker::new(&target);
435
436        target.set(s("foo"), s("bar"));
437
438        let (action, new_items, _, new_starting_index, _) = tracker.args();
439        assert_eq!(NotifyCollectionChangedAction::Add, action);
440        assert_eq!(-1, new_starting_index);
441        assert_eq!(1, new_items.len());
442        assert_eq!(pair("foo", "bar"), new_items[0]);
443    }
444
445    #[test]
446    fn assigning_item_should_raise_collection_changed_replace() {
447        let target = FerroDictionary::<String, String>::new();
448
449        target.set(s("foo"), s("baz"));
450        let tracker = CollectionChangedTracker::new(&target);
451        target.set(s("foo"), s("bar"));
452
453        let (action, new_items, _, new_starting_index, _) = tracker.args();
454        assert_eq!(NotifyCollectionChangedAction::Replace, action);
455        assert_eq!(-1, new_starting_index);
456        assert_eq!(1, new_items.len());
457        assert_eq!(pair("foo", "bar"), new_items[0]);
458    }
459
460    #[test]
461    fn assigning_item_should_raise_property_changed_add() {
462        let target = FerroDictionary::<String, String>::new();
463        let tracker = PropertyChangedTracker::new(&target);
464
465        target.set(s("foo"), s("bar"));
466
467        assert_eq!(vec!["Count", "Item[foo]"], tracker.names());
468    }
469
470    #[test]
471    fn assigning_item_should_raise_property_changed_replace() {
472        let target = FerroDictionary::<String, String>::new();
473
474        target.set(s("foo"), s("baz"));
475        let tracker = PropertyChangedTracker::new(&target);
476        target.set(s("foo"), s("bar"));
477
478        assert_eq!(vec!["Item[foo]"], tracker.names());
479    }
480
481    #[test]
482    fn removing_item_should_raise_collection_changed() {
483        let target = FerroDictionary::<String, String>::new();
484
485        target.set(s("foo"), s("bar"));
486        let tracker = CollectionChangedTracker::new(&target);
487        target.remove(&s("foo"));
488
489        let (action, _, old_items, _, old_starting_index) = tracker.args();
490        assert_eq!(NotifyCollectionChangedAction::Remove, action);
491        assert_eq!(-1, old_starting_index);
492        assert_eq!(1, old_items.len());
493        assert_eq!(pair("foo", "bar"), old_items[0]);
494    }
495
496    #[test]
497    fn remove_method_should_remove_item_from_collection() {
498        let target = FerroDictionary::from_dictionary([pair("foo", "bar")]);
499        assert_eq!(target.count(), 1);
500
501        target.remove(&s("foo"));
502        assert_eq!(target.count(), 0);
503    }
504
505    #[test]
506    fn removing_item_should_raise_property_changed() {
507        let target = FerroDictionary::<String, String>::new();
508
509        target.set(s("foo"), s("bar"));
510        let tracker = PropertyChangedTracker::new(&target);
511        target.remove(&s("foo"));
512
513        assert_eq!(vec!["Count", "Item[foo]"], tracker.names());
514    }
515
516    #[test]
517    fn clearing_collection_should_raise_collection_changed() {
518        let target = FerroDictionary::<String, String>::new();
519
520        target.set(s("foo"), s("bar"));
521        target.set(s("baz"), s("qux"));
522        let tracker = CollectionChangedTracker::new(&target);
523        target.clear();
524
525        let (action, _, old_items, _, old_starting_index) = tracker.args();
526        assert_eq!(NotifyCollectionChangedAction::Remove, action);
527        assert_eq!(-1, old_starting_index);
528        assert_eq!(2, old_items.len());
529        assert_eq!(pair("foo", "bar"), old_items[0]);
530    }
531
532    #[test]
533    fn clearing_collection_should_raise_property_changed() {
534        let target = FerroDictionary::<String, String>::new();
535
536        target.set(s("foo"), s("bar"));
537        target.set(s("baz"), s("qux"));
538        let tracker = PropertyChangedTracker::new(&target);
539        target.clear();
540
541        assert_eq!(vec!["Count", INDEXER_NAME], tracker.names());
542    }
543
544    #[test]
545    fn constructor_should_initialize_with_provided_collection() {
546        let initial_collection = HashMap::from([pair("key1", "value1"), pair("key2", "value2")]);
547
548        let target = FerroDictionary::from_dictionary(initial_collection);
549
550        assert_eq!(2, target.count());
551        assert_eq!("value1", target.get(&s("key1")));
552        assert_eq!("value2", target.get(&s("key2")));
553    }
554
555    // Not upstream: the enumeration order the notifications above rely on.
556    #[test]
557    fn enumeration_follows_insertion_and_reuses_removed_slots() {
558        let target = FerroDictionary::<String, i32>::new();
559        target.add(s("a"), 1);
560        target.add(s("b"), 2);
561        target.add(s("c"), 3);
562        assert_eq!(vec![s("a"), s("b"), s("c")], target.keys());
563
564        target.remove(&s("b"));
565        target.add(s("d"), 4);
566        assert_eq!(vec![s("a"), s("d"), s("c")], target.keys());
567        assert_eq!(vec![1, 4, 3], target.values());
568        assert!(target.contains_key(&s("d")));
569        assert!(!target.contains_key(&s("b")));
570        assert!(target.contains(&s("d"), &4));
571        assert_eq!(None, target.try_get_value(&s("b")));
572    }
573
574    // Not upstream: `CopyTo` has no test there.
575    #[test]
576    fn copy_to_copies_the_entries_in_enumeration_order() {
577        let target = FerroDictionary::<String, i32>::new();
578        target.add(s("a"), 1);
579        target.add(s("b"), 2);
580        let mut array = vec![(s(""), 0); 3];
581
582        target.copy_to(&mut array, 1);
583
584        assert_eq!(vec![(s(""), 0), (s("a"), 1), (s("b"), 2)], array);
585    }
586
587    // Not upstream: the argument check of `CopyTo`.
588    #[test]
589    #[should_panic(expected = "Destination array is not long enough")]
590    fn copy_to_panics_when_the_array_is_too_small() {
591        let target = FerroDictionary::<String, i32>::new();
592        target.add(s("a"), 1);
593        target.add(s("b"), 2);
594        let mut array = vec![(s(""), 0); 2];
595
596        target.copy_to(&mut array, 1);
597    }
598}