#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::{borrow::Borrow, ffi::c_void, hash::Hash, mem};
use crate::{
kCFTypeDictionaryKeyCallBacks, kCFTypeDictionaryValueCallBacks, CFDictionary, CFIndex,
CFMutableDictionary, CFRetained, Type,
};
#[cold]
fn failed_creating_dictionary(len: CFIndex) -> ! {
#[cfg(feature = "alloc")]
{
use alloc::alloc::{handle_alloc_error, Layout};
use core::mem::align_of;
let layout =
Layout::array::<(*const (), *const ())>(len as usize).unwrap_or_else(|_| unsafe {
Layout::from_size_align_unchecked(0, align_of::<*const ()>())
});
handle_alloc_error(layout)
}
#[cfg(not(feature = "alloc"))]
{
panic!("failed allocating CFDictionary holding {len} elements")
}
}
#[inline]
fn to_void<K: ?Sized + Type>(key: &K) -> *const c_void {
let key: *const K = key;
key.cast()
}
impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
#[inline]
#[doc(alias = "CFDictionaryCreate")]
pub fn empty() -> CFRetained<Self>
where
K: Type + PartialEq + Hash,
V: Type,
{
Self::from_slices(&[], &[])
}
#[inline]
#[doc(alias = "CFDictionaryCreate")]
pub fn from_slices(keys: &[&K], values: &[&V]) -> CFRetained<Self>
where
K: Type + PartialEq + Hash,
V: Type,
{
assert_eq!(
keys.len(),
values.len(),
"key and object slices must have the same length",
);
debug_assert!(keys.len() < CFIndex::MAX as usize);
let len = keys.len() as CFIndex;
let keys = keys.as_ptr().cast::<*const c_void>().cast_mut();
let values = values.as_ptr().cast::<*const c_void>().cast_mut();
let dictionary = unsafe {
CFDictionary::new(
None,
keys,
values,
len,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
)
}
.unwrap_or_else(|| failed_creating_dictionary(len));
unsafe { CFRetained::cast_unchecked::<Self>(dictionary) }
}
}
impl<K: ?Sized, V: ?Sized> CFMutableDictionary<K, V> {
#[inline]
#[doc(alias = "CFDictionaryCreateMutable")]
pub fn empty() -> CFRetained<Self>
where
K: Type + PartialEq + Hash,
V: Type,
{
Self::with_capacity(0)
}
#[inline]
#[doc(alias = "CFDictionaryCreateMutable")]
pub fn with_capacity(capacity: usize) -> CFRetained<Self>
where
K: Type + PartialEq + Hash,
V: Type,
{
let capacity = capacity.try_into().expect("capacity too high");
let dictionary = unsafe {
CFMutableDictionary::new(
None,
capacity,
&kCFTypeDictionaryKeyCallBacks,
&kCFTypeDictionaryValueCallBacks,
)
}
.unwrap_or_else(|| failed_creating_dictionary(capacity));
unsafe { CFRetained::cast_unchecked::<Self>(dictionary) }
}
}
impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
#[inline]
#[doc(alias = "CFDictionaryGetValue")]
pub unsafe fn get_unchecked(&self, key: &K) -> Option<&V>
where
K: Type + Sized,
V: Type + Sized,
{
let value = unsafe { self.as_opaque().value(to_void(key)) };
unsafe { value.cast::<V>().as_ref() }
}
#[cfg(feature = "alloc")]
pub unsafe fn to_vecs_unchecked(&self) -> (Vec<&K>, Vec<&V>)
where
K: Type,
V: Type,
{
let len = self.len();
let mut keys = Vec::<&K>::with_capacity(len);
let mut values = Vec::<&V>::with_capacity(len);
let keys_ptr = keys.as_mut_ptr().cast::<*const c_void>();
let values_ptr = values.as_mut_ptr().cast::<*const c_void>();
unsafe { self.as_opaque().keys_and_values(keys_ptr, values_ptr) };
unsafe {
keys.set_len(len);
values.set_len(len);
}
(keys, values)
}
}
impl<K: ?Sized, V: ?Sized> CFDictionary<K, V> {
pub fn as_opaque(&self) -> &CFDictionary {
unsafe { mem::transmute::<&CFDictionary<K, V>, &CFDictionary>(self) }
}
#[inline]
#[doc(alias = "CFDictionaryGetCount")]
pub fn len(&self) -> usize {
self.as_opaque().count() as _
}
#[inline]
pub fn is_empty(&self) -> bool {
self.len() == 0
}
#[doc(alias = "CFDictionaryGetValue")]
pub fn get(&self, key: &K) -> Option<CFRetained<V>>
where
K: Type + Sized + PartialEq + Hash,
V: Type + Sized,
{
unsafe { self.get_unchecked(key) }.map(V::retain)
}
#[cfg(feature = "alloc")]
#[doc(alias = "CFDictionaryGetKeysAndValues")]
pub fn to_vecs(&self) -> (Vec<CFRetained<K>>, Vec<CFRetained<V>>)
where
K: Type + Sized,
V: Type + Sized,
{
let (keys, objects) = unsafe { self.to_vecs_unchecked() };
(
keys.into_iter().map(K::retain).collect(),
objects.into_iter().map(V::retain).collect(),
)
}
#[inline]
#[doc(alias = "CFDictionaryContainsKey")]
pub fn contains_key(&self, key: &K) -> bool
where
K: Type + Sized + PartialEq + Hash,
{
unsafe { self.as_opaque().contains_ptr_key(to_void(key)) }
}
#[inline]
#[doc(alias = "CFDictionaryContainsValue")]
pub fn contains_value(&self, value: &V) -> bool
where
V: Type + Sized + PartialEq,
{
unsafe { self.as_opaque().contains_ptr_value(to_void(value)) }
}
}
impl<K: ?Sized, V: ?Sized> CFMutableDictionary<K, V> {
pub fn as_opaque(&self) -> &CFMutableDictionary {
unsafe { mem::transmute::<&CFMutableDictionary<K, V>, &CFMutableDictionary>(self) }
}
#[inline]
#[doc(alias = "CFDictionaryAddValue")]
pub fn add(&self, key: &K, value: &V)
where
K: Type + Sized + PartialEq + Hash,
V: Type + Sized,
{
unsafe {
CFMutableDictionary::add_value(Some(self.as_opaque()), to_void(key), to_void(value))
}
}
#[inline]
#[doc(alias = "CFDictionarySetValue")]
pub fn set(&self, key: &K, value: &V)
where
K: Type + Sized + PartialEq + Hash,
V: Type + Sized,
{
unsafe {
CFMutableDictionary::set_value(Some(self.as_opaque()), to_void(key), to_void(value))
}
}
#[inline]
#[doc(alias = "CFDictionaryReplaceValue")]
pub fn replace(&self, key: &K, value: &V)
where
K: Type + Sized + PartialEq + Hash,
V: Type + Sized,
{
unsafe {
CFMutableDictionary::replace_value(Some(self.as_opaque()), to_void(key), to_void(value))
}
}
#[inline]
#[doc(alias = "CFDictionaryRemoveValue")]
pub fn remove(&self, key: &K)
where
K: Type + Sized + PartialEq + Hash,
{
unsafe { CFMutableDictionary::remove_value(Some(self.as_opaque()), to_void(key)) }
}
#[inline]
#[doc(alias = "CFDictionaryRemoveAllValues")]
pub fn clear(&self) {
CFMutableDictionary::remove_all_values(Some(self.as_opaque()))
}
}
impl<K: ?Sized + Type, V: ?Sized + Type> AsRef<CFDictionary> for CFDictionary<K, V> {
fn as_ref(&self) -> &CFDictionary {
self.as_opaque()
}
}
impl<K: ?Sized + Type, V: ?Sized + Type> AsRef<CFMutableDictionary> for CFMutableDictionary<K, V> {
fn as_ref(&self) -> &CFMutableDictionary {
self.as_opaque()
}
}
impl<K: ?Sized + Type, V: ?Sized + Type> Borrow<CFDictionary> for CFDictionary<K, V> {
fn borrow(&self) -> &CFDictionary {
self.as_opaque()
}
}
impl<K: ?Sized + Type, V: ?Sized + Type> Borrow<CFMutableDictionary> for CFMutableDictionary<K, V> {
fn borrow(&self) -> &CFMutableDictionary {
self.as_opaque()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::CFType;
#[test]
fn empty() {
let dict1 = CFDictionary::<CFType, CFType>::empty();
let dict2 = CFDictionary::<CFType, CFType>::empty();
assert_eq!(dict1, dict2);
assert_eq!(dict1.len(), 0);
assert_eq!(dict1.get(dict1.as_ref()), None);
assert!(!dict1.contains_key(dict1.as_ref()));
assert!(!dict1.contains_value(dict1.as_ref()));
#[cfg(feature = "alloc")]
assert_eq!(dict1.to_vecs(), (alloc::vec![], alloc::vec![]));
}
#[test]
#[cfg(feature = "CFString")]
fn mutable_dictionary() {
use crate::CFString;
let dict = CFMutableDictionary::<CFString, CFString>::empty();
dict.add(&CFString::from_str("a"), &CFString::from_str("b"));
dict.add(&CFString::from_str("c"), &CFString::from_str("d"));
assert_eq!(dict.len(), 2);
dict.add(&CFString::from_str("c"), &CFString::from_str("e"));
assert_eq!(dict.len(), 2);
assert_eq!(
dict.get(&CFString::from_str("c")),
Some(CFString::from_str("d")),
);
dict.replace(&CFString::from_str("c"), &CFString::from_str("f"));
assert_eq!(dict.len(), 2);
assert_eq!(
dict.get(&CFString::from_str("c")),
Some(CFString::from_str("f"))
);
dict.remove(&CFString::from_str("a"));
assert_eq!(dict.len(), 1);
dict.clear();
assert_eq!(dict.len(), 0);
}
#[test]
#[cfg(feature = "CFString")]
fn contains() {
use crate::CFString;
let dict = CFDictionary::from_slices(
&[&*CFString::from_str("key")],
&[&*CFString::from_str("value")],
);
assert!(dict.contains_key(&CFString::from_str("key")));
assert!(dict.get(&CFString::from_str("key")).is_some());
assert!(!dict.contains_key(&CFString::from_str("invalid key")));
assert!(dict.get(&CFString::from_str("invalid key")).is_none());
assert!(dict.contains_value(&CFString::from_str("value")));
assert!(!dict.contains_value(&CFString::from_str("invalid value")));
}
#[test]
#[cfg(all(feature = "CFString", feature = "CFNumber"))]
fn heterogenous() {
use crate::{CFBoolean, CFNumber, CFString, CFType};
let dict = CFDictionary::<CFType, CFType>::from_slices(
&[
CFString::from_str("string key").as_ref(),
CFNumber::new_isize(4).as_ref(),
CFBoolean::new(true).as_ref(),
],
&[
CFString::from_str("a string value").as_ref(),
CFNumber::new_isize(2).as_ref(),
CFBoolean::new(false).as_ref(),
],
);
assert_eq!(
dict.get(&CFString::from_str("string key")),
Some(CFString::from_str("a string value").into())
);
assert_eq!(
dict.get(&CFNumber::new_isize(4)),
Some(CFNumber::new_isize(2).into())
);
assert_eq!(
dict.get(CFBoolean::new(true)),
Some(CFBoolean::new(false).into())
);
assert_eq!(dict.get(CFBoolean::new(false)), None);
}
#[test]
#[cfg(feature = "CFString")]
#[should_panic = "key and object slices must have the same length"]
fn from_slices_not_same_length() {
use crate::CFString;
let _dict =
CFDictionary::<CFString, CFString>::from_slices(&[&*CFString::from_str("key")], &[]);
}
}