use crate::ffi;
use rustc_hash::FxHashMap;
use std::cell::UnsafeCell;
use std::ffi::{CStr, CString};
use std::hash::{Hash, Hasher};
use std::ptr::NonNull;
thread_local! {
static NAME_CACHE: UnsafeCell<FxHashMap<Box<str>, Name>> = UnsafeCell::new(FxHashMap::default());
}
pub fn clear_name_cache() {
NAME_CACHE.with(|cache| {
unsafe { (*cache.get()).clear() };
});
}
pub fn name_cache_size() -> usize {
NAME_CACHE.with(|cache| {
unsafe { (*cache.get()).len() }
})
}
#[repr(transparent)]
pub struct Name(NonNull<ffi::blpapi_Name_t>);
impl Name {
#[cold]
pub fn new(s: &str) -> Option<Self> {
let c = CString::new(s).ok()?;
let ptr = unsafe { ffi::blpapi_Name_create(c.as_ptr()) };
NonNull::new(ptr).map(Self)
}
pub fn find(s: &str) -> Option<Self> {
let c = CString::new(s).ok()?;
let ptr = unsafe { ffi::blpapi_Name_findName(c.as_ptr()) };
NonNull::new(ptr).map(Self)
}
#[inline]
pub fn get_or_intern(s: &str) -> Self {
NAME_CACHE.with(|cache| {
let cache = unsafe { &mut *cache.get() };
if let Some(name) = cache.get(s) {
return name.clone();
}
let name = Self::new(s).expect("failed to intern name");
cache.insert(s.into(), name.clone());
name
})
}
#[inline]
pub fn try_get_or_intern(s: &str) -> Option<Self> {
NAME_CACHE.with(|cache| {
let cache = unsafe { &mut *cache.get() };
if let Some(name) = cache.get(s) {
return Some(name.clone());
}
let name = Self::new(s)?;
cache.insert(s.into(), name.clone());
Some(name)
})
}
#[inline(always)]
pub fn as_str(&self) -> &str {
let ptr = unsafe { ffi::blpapi_Name_string(self.0.as_ptr()) };
unsafe { CStr::from_ptr(ptr) }
.to_str()
.expect("Bloomberg Name contained invalid UTF-8")
}
#[inline]
pub(crate) unsafe fn from_raw(ptr: NonNull<ffi::blpapi_Name_t>) -> Self {
Self(ptr)
}
#[inline(always)]
pub fn as_ptr(&self) -> *mut ffi::blpapi_Name_t {
self.0.as_ptr()
}
}
impl Clone for Name {
fn clone(&self) -> Self {
let ptr = unsafe { ffi::blpapi_Name_duplicate(self.0.as_ptr()) };
Self(NonNull::new(ptr).expect("blpapi_Name_duplicate returned null"))
}
}
impl Drop for Name {
fn drop(&mut self) {
unsafe { ffi::blpapi_Name_destroy(self.0.as_ptr()) }
}
}
impl PartialEq for Name {
#[inline(always)]
fn eq(&self, other: &Self) -> bool {
self.0 == other.0 }
}
impl Eq for Name {}
impl Hash for Name {
#[inline(always)]
fn hash<H: Hasher>(&self, state: &mut H) {
self.0.as_ptr().hash(state)
}
}
impl std::fmt::Debug for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("Name").field(&self.as_str()).finish()
}
}
impl std::fmt::Display for Name {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(self.as_str())
}
}
#[cfg(all(test, feature = "live"))]
mod tests {
use super::*;
#[test]
fn test_name_interning() {
let name1 = Name::new("TEST_NAME").expect("failed to create name");
let name2 = Name::new("TEST_NAME").expect("failed to create name");
assert_eq!(name1, name2);
assert_eq!(name1.as_ptr(), name2.as_ptr());
}
#[test]
fn test_name_as_str_roundtrip() {
let name = Name::new("PX_LAST").expect("failed to create name");
assert_eq!(name.as_str(), "PX_LAST");
}
#[test]
fn test_name_display() {
let name = Name::new("SECURITY_DATA").expect("failed to create name");
assert_eq!(format!("{}", name), "SECURITY_DATA");
}
#[test]
fn test_name_debug() {
let name = Name::new("FIELD_DATA").expect("failed to create name");
assert_eq!(format!("{:?}", name), "Name(\"FIELD_DATA\")");
}
}