// Copyright 2013 The Servo Project Developers. See the COPYRIGHT // file at the top-level directory of this distribution. // // Licensed under the Apache License, Version 2.0 or the MIT license // , at your // option. This file may not be copied, modified, or distributed // except according to those terms. //! Dictionaries of key-value pairs. pub use core_foundation_sys::dictionary::*; use core_foundation_sys::base::{kCFAllocatorDefault, CFRelease, CFTypeRef}; use std::marker::PhantomData; use std::mem; use std::os::raw::c_void; use std::ptr; use crate::base::{CFIndexConvertible, TCFType}; use crate::base::{FromVoid, ItemRef, ToVoid}; use crate::ConcreteCFType; // consume the type parameters with PhantomDatas pub struct CFDictionary( CFDictionaryRef, PhantomData, PhantomData, ); impl Drop for CFDictionary { fn drop(&mut self) { unsafe { CFRelease(self.as_CFTypeRef()) } } } impl_TCFType!(CFDictionary, CFDictionaryRef, CFDictionaryGetTypeID); impl_CFTypeDescription!(CFDictionary); unsafe impl ConcreteCFType for CFDictionary<*const c_void, *const c_void> {} impl CFDictionary { pub fn from_CFType_pairs(pairs: &[(K, V)]) -> CFDictionary where K: TCFType, V: TCFType, { let (keys, values): (Vec, Vec) = pairs .iter() .map(|(key, value)| (key.as_CFTypeRef(), value.as_CFTypeRef())) .unzip(); unsafe { let dictionary_ref = CFDictionaryCreate( kCFAllocatorDefault, keys.as_ptr(), values.as_ptr(), keys.len().to_CFIndex(), &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks, ); TCFType::wrap_under_create_rule(dictionary_ref) } } #[inline] pub fn to_untyped(&self) -> CFDictionary { unsafe { CFDictionary::wrap_under_get_rule(self.0) } } /// Returns a `CFMutableDictionary` pointing to the same underlying dictionary as this immutable one. /// This should only be used when the underlying dictionary is mutable. #[inline] pub unsafe fn to_mutable(&self) -> CFMutableDictionary { CFMutableDictionary::wrap_under_get_rule(self.0 as CFMutableDictionaryRef) } /// Returns the same dictionary, but with the types reset to void pointers. /// Equal to `to_untyped`, but is faster since it does not increment the retain count. #[inline] pub fn into_untyped(self) -> CFDictionary { let reference = self.0; mem::forget(self); unsafe { CFDictionary::wrap_under_create_rule(reference) } } #[inline] pub fn len(&self) -> usize { unsafe { CFDictionaryGetCount(self.0) as usize } } #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } #[inline] pub fn contains_key(&self, key: &K) -> bool where K: ToVoid, { unsafe { CFDictionaryContainsKey(self.0, key.to_void()) != 0 } } #[inline] pub fn find>(&self, key: T) -> Option> where V: FromVoid, K: ToVoid, { unsafe { let mut value: *const c_void = ptr::null(); if CFDictionaryGetValueIfPresent(self.0, key.to_void(), &mut value) != 0 { Some(V::from_void(value)) } else { None } } } /// # Panics /// /// Panics if the key is not present in the dictionary. Use `find` to get an `Option` instead /// of panicking. #[inline] pub fn get>(&self, key: T) -> ItemRef<'_, V> where V: FromVoid, K: ToVoid, { let ptr = key.to_void(); self.find(key) .unwrap_or_else(|| panic!("No entry found for key {:p}", ptr)) } pub fn get_keys_and_values(&self) -> (Vec<*const c_void>, Vec<*const c_void>) { let length = self.len(); let mut keys = Vec::with_capacity(length); let mut values = Vec::with_capacity(length); unsafe { CFDictionaryGetKeysAndValues(self.0, keys.as_mut_ptr(), values.as_mut_ptr()); keys.set_len(length); values.set_len(length); } (keys, values) } } // consume the type parameters with PhantomDatas pub struct CFMutableDictionary( CFMutableDictionaryRef, PhantomData, PhantomData, ); impl Drop for CFMutableDictionary { fn drop(&mut self) { unsafe { CFRelease(self.as_CFTypeRef()) } } } impl_TCFType!(CFMutableDictionary, CFMutableDictionaryRef, CFDictionaryGetTypeID); impl_CFTypeDescription!(CFMutableDictionary); impl CFMutableDictionary { pub fn new() -> Self { Self::with_capacity(0) } pub fn with_capacity(capacity: isize) -> Self { unsafe { let dictionary_ref = CFDictionaryCreateMutable( kCFAllocatorDefault, capacity as _, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks, ); TCFType::wrap_under_create_rule(dictionary_ref) } } pub fn copy_with_capacity(&self, capacity: isize) -> Self { unsafe { let dictionary_ref = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, capacity as _, self.0); TCFType::wrap_under_get_rule(dictionary_ref) } } pub fn from_CFType_pairs(pairs: &[(K, V)]) -> CFMutableDictionary where K: ToVoid, V: ToVoid, { let mut result = Self::with_capacity(pairs.len() as _); for (key, value) in pairs { result.add(key, value); } result } #[inline] pub fn to_untyped(&self) -> CFMutableDictionary { unsafe { CFMutableDictionary::wrap_under_get_rule(self.0) } } /// Returns the same dictionary, but with the types reset to void pointers. /// Equal to `to_untyped`, but is faster since it does not increment the retain count. #[inline] pub fn into_untyped(self) -> CFMutableDictionary { let reference = self.0; mem::forget(self); unsafe { CFMutableDictionary::wrap_under_create_rule(reference) } } /// Returns a `CFDictionary` pointing to the same underlying dictionary as this mutable one. #[inline] pub fn to_immutable(&self) -> CFDictionary { unsafe { CFDictionary::wrap_under_get_rule(self.0) } } // Immutable interface #[inline] pub fn len(&self) -> usize { unsafe { CFDictionaryGetCount(self.0) as usize } } #[inline] pub fn is_empty(&self) -> bool { self.len() == 0 } #[inline] pub fn contains_key(&self, key: *const c_void) -> bool { unsafe { CFDictionaryContainsKey(self.0, key) != 0 } } #[inline] pub fn find<'a>(&'a self, key: &K) -> Option> where V: FromVoid, K: ToVoid, { unsafe { let mut value: *const c_void = ptr::null(); if CFDictionaryGetValueIfPresent(self.0, key.to_void(), &mut value) != 0 { Some(V::from_void(value)) } else { None } } } /// # Panics /// /// Panics if the key is not present in the dictionary. Use `find` to get an `Option` instead /// of panicking. #[inline] pub fn get<'a>(&'a self, key: &K) -> ItemRef<'a, V> where V: FromVoid, K: ToVoid, { let ptr = key.to_void(); self.find(key) .unwrap_or_else(|| panic!("No entry found for key {:p}", ptr)) } pub fn get_keys_and_values(&self) -> (Vec<*const c_void>, Vec<*const c_void>) { let length = self.len(); let mut keys = Vec::with_capacity(length); let mut values = Vec::with_capacity(length); unsafe { CFDictionaryGetKeysAndValues(self.0, keys.as_mut_ptr(), values.as_mut_ptr()); keys.set_len(length); values.set_len(length); } (keys, values) } // Mutable interface /// Adds the key-value pair to the dictionary if no such key already exist. #[inline] pub fn add(&mut self, key: &K, value: &V) where K: ToVoid, V: ToVoid, { unsafe { CFDictionaryAddValue(self.0, key.to_void(), value.to_void()) } } /// Sets the value of the key in the dictionary. #[inline] pub fn set(&mut self, key: K, value: V) where K: ToVoid, V: ToVoid, { unsafe { CFDictionarySetValue(self.0, key.to_void(), value.to_void()) } } /// Replaces the value of the key in the dictionary. #[inline] pub fn replace(&mut self, key: K, value: V) where K: ToVoid, V: ToVoid, { unsafe { CFDictionaryReplaceValue(self.0, key.to_void(), value.to_void()) } } /// Removes the value of the key from the dictionary. #[inline] pub fn remove(&mut self, key: K) where K: ToVoid, { unsafe { CFDictionaryRemoveValue(self.0, key.to_void()) } } #[inline] pub fn remove_all(&mut self) { unsafe { CFDictionaryRemoveAllValues(self.0) } } } impl Default for CFMutableDictionary { fn default() -> Self { Self::new() } } impl<'a, K, V> From<&'a CFDictionary> for CFMutableDictionary { /// Creates a new mutable dictionary with the key-value pairs from another dictionary. /// The capacity of the new mutable dictionary is not limited. fn from(dict: &'a CFDictionary) -> Self { unsafe { let mut_dict_ref = CFDictionaryCreateMutableCopy(kCFAllocatorDefault, 0, dict.0); TCFType::wrap_under_create_rule(mut_dict_ref) } } } #[cfg(test)] pub mod test { use super::*; use crate::base::{CFType, TCFType}; use crate::boolean::CFBoolean; use crate::number::CFNumber; use crate::string::CFString; #[test] fn dictionary() { let bar = CFString::from_static_string("Bar"); let baz = CFString::from_static_string("Baz"); let boo = CFString::from_static_string("Boo"); let foo = CFString::from_static_string("Foo"); let tru = CFBoolean::true_value(); let n42 = CFNumber::from(42); let d = CFDictionary::from_CFType_pairs(&[ (bar.as_CFType(), boo.as_CFType()), (baz.as_CFType(), tru.as_CFType()), (foo.as_CFType(), n42.as_CFType()), ]); let (v1, v2) = d.get_keys_and_values(); assert_eq!( v1, &[bar.as_CFTypeRef(), baz.as_CFTypeRef(), foo.as_CFTypeRef()] ); assert_eq!( v2, &[boo.as_CFTypeRef(), tru.as_CFTypeRef(), n42.as_CFTypeRef()] ); } #[test] fn mutable_dictionary() { let bar = CFString::from_static_string("Bar"); let baz = CFString::from_static_string("Baz"); let boo = CFString::from_static_string("Boo"); let foo = CFString::from_static_string("Foo"); let tru = CFBoolean::true_value(); let n42 = CFNumber::from(42); let mut d = CFMutableDictionary::::new(); d.add(&bar, &boo.as_CFType()); d.add(&baz, &tru.as_CFType()); d.add(&foo, &n42.as_CFType()); assert_eq!(d.len(), 3); let (v1, v2) = d.get_keys_and_values(); assert_eq!( v1, &[bar.as_CFTypeRef(), baz.as_CFTypeRef(), foo.as_CFTypeRef()] ); assert_eq!( v2, &[boo.as_CFTypeRef(), tru.as_CFTypeRef(), n42.as_CFTypeRef()] ); d.remove(baz); assert_eq!(d.len(), 2); let (v1, v2) = d.get_keys_and_values(); assert_eq!(v1, &[bar.as_CFTypeRef(), foo.as_CFTypeRef()]); assert_eq!(v2, &[boo.as_CFTypeRef(), n42.as_CFTypeRef()]); d.remove_all(); assert_eq!(d.len(), 0) } #[test] fn dict_find_and_contains_key() { let dict = CFDictionary::from_CFType_pairs(&[( CFString::from_static_string("hello"), CFBoolean::true_value(), )]); let key = CFString::from_static_string("hello"); let invalid_key = CFString::from_static_string("foobar"); assert!(dict.contains_key(&key)); assert!(!dict.contains_key(&invalid_key)); let value = dict.find(&key).unwrap().clone(); assert_eq!(value, CFBoolean::true_value()); assert_eq!(dict.find(&invalid_key), None); } #[test] fn convert_immutable_to_mutable_dict() { let dict: CFDictionary = CFDictionary::from_CFType_pairs(&[( CFString::from_static_string("Foo"), CFBoolean::true_value(), )]); let mut mut_dict = CFMutableDictionary::from(&dict); assert_eq!(dict.retain_count(), 1); assert_eq!(mut_dict.retain_count(), 1); assert_eq!(mut_dict.len(), 1); assert_eq!( *mut_dict.get(&CFString::from_static_string("Foo")), CFBoolean::true_value() ); mut_dict.add( &CFString::from_static_string("Bar"), &CFBoolean::false_value(), ); assert_eq!(dict.len(), 1); assert_eq!(mut_dict.len(), 2); } #[test] fn mutable_dictionary_as_immutable() { let mut mut_dict: CFMutableDictionary = CFMutableDictionary::new(); mut_dict.add( &CFString::from_static_string("Bar"), &CFBoolean::false_value(), ); assert_eq!(mut_dict.retain_count(), 1); let dict = mut_dict.to_immutable(); assert_eq!(mut_dict.retain_count(), 2); assert_eq!(dict.retain_count(), 2); assert_eq!( *dict.get(&CFString::from_static_string("Bar")), CFBoolean::false_value() ); mem::drop(dict); assert_eq!(mut_dict.retain_count(), 1); } }