use std::hash::Hash;
use std::borrow::{Borrow, ToOwned};
#[cfg(debug_assertions)] use std::sync::atomic::{self, AtomicUsize, Ordering};
use traits::{Intern, Resolve, ResolveUnchecked, Len, SymbolId};
use {core, Result, ErrorKind};
use sym::{Symbol as ISymbol, Pool as IPool};
#[cfg(debug_assertions)]
static NEXT_POOL_ID: AtomicUsize = atomic::ATOMIC_USIZE_INIT;
#[cfg(feature = "fnv")]
type HashMap<K, V> = ::fnv::FnvHashMap<K, V>;
#[cfg(not(feature = "fnv"))]
type HashMap<K, V> = ::std::collections::HashMap<K, V>;
make_sym! {
pub Sym<I>:
"Symbol type used by [`Pool`](struct.Pool.html)'s [`Intern`](../traits/trait.Intern.html) and [`Resolve`](../traits/trait.Resolve.html) implementations.";
}
#[derive(Debug)]
pub struct Pool<T: ?Sized, I = usize>
where T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash,
I: SymbolId
{
ids_map: HashMap<u64, I>,
lookup_vec: Vec<T::Owned>,
#[cfg(debug_assertions)]
pool_id: usize
}
impl<T: ?Sized, I> Clone for Pool<T, I>
where T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash + Clone,
I: SymbolId,
{
#[cfg(debug_assertions)]
fn clone(&self) -> Self {
Pool{ids_map: self.ids_map.clone(),
lookup_vec: self.lookup_vec.clone(),
pool_id: self.pool_id}
}
#[cfg(not(debug_assertions))]
fn clone(&self) -> Self {
Pool{ids_map: self.ids_map.clone(),
lookup_vec: self.lookup_vec.clone()}
}
}
impl<T: ?Sized, I> Pool<T, I>
where T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash,
I: SymbolId
{
pub fn new() -> Self {
Default::default()
}
}
impl<'a, T: ?Sized, I> Len for Pool<T, I>
where T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash,
I: SymbolId
{
fn len(&self) -> usize {
self.lookup_vec.len()
}
fn is_empty(&self) -> bool {
self.lookup_vec.is_empty()
}
fn is_full(&self) -> bool {
let len = self.len();
len >= 1 && len - 1 >= I::max_value().to_usize().expect("Unexpected failure to convert index type `max_value()` result to usize")
}
}
impl<'a, T: ?Sized, I> ::sym::Pool for Pool<T, I>
where T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash,
I: SymbolId
{
type Symbol = Sym<I>;
#[cfg(debug_assertions)]
fn id(&self) -> ::sym::PoolId {
self.pool_id
}
#[cfg(not(debug_assertions))]
fn create_symbol(&self, id: <Self::Symbol as ::sym::Symbol>::Id) -> Self::Symbol {
Sym::create(id)
}
#[cfg(debug_assertions)]
fn create_symbol(&self, id: <Self::Symbol as ::sym::Symbol>::Id) -> Self::Symbol {
Sym::create(id, self.id())
}
}
impl<T: ?Sized, I> Default for Pool<T, I>
where T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash,
I: SymbolId
{
#[cfg(not(debug_assertions))]
fn default() -> Self {
Pool{ids_map: Default::default(),
lookup_vec: Default::default()}
}
#[cfg(debug_assertions)]
fn default() -> Self {
Pool{ids_map: Default::default(),
lookup_vec: Default::default(),
pool_id: NEXT_POOL_ID.fetch_add(1, Ordering::SeqCst)}
}
}
impl<'a, T: ?Sized, I> Intern for &'a mut Pool<T, I>
where I: SymbolId,
T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash + Borrow<T>,
{
type Input = T;
type Symbol = Sym<I>;
fn intern(mut self, value: &Self::Input) -> Result<Self::Symbol> {
let key = core::hash::<T, core::DefaultHashAlgo>(value);
if let Some(&id) = self.ids_map.get(&key) {
return Ok(self.create_symbol(id))
} else if self.is_full() {
return Err(ErrorKind::PoolOverflow.into())
} else {
self.lookup_vec.push(value.to_owned());
let id = I::from_usize(self.lookup_vec.len() - 1)
.expect("Unexpected failure to convert symbol ID from usize");
self.ids_map.insert(key, id);
Ok(self.create_symbol(id))
}
}
}
#[cfg(debug_assertions)]
macro_rules! check_matching_pool {
($slf: ident, $sym: ident) => {
if $sym.pool_id() != $slf.id() {
panic!(concat!("\nDetected an invalid attempt to resolve a symbol on a pool that did not\n",
"create it. This is a bug in the program or library using Symtern; do not\n",
"report it to the Symtern developers."));
}
};
}
#[cfg(not(debug_assertions))]
macro_rules! check_matching_pool {
($slf: ident, $sym: ident) => {};
}
impl<'a,T: ?Sized, I> Resolve for &'a Pool<T, I>
where T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash + Borrow<T>,
I: SymbolId
{
type Input = <&'a mut Pool<T, I> as Intern>::Symbol;
type Output = &'a T;
fn resolve(self, s: Self::Input) -> Result<Self::Output> {
check_matching_pool!(self, s);
let idx = s.id().to_usize().expect("Unexpected failure to convert symbol ID to usize");
if self.lookup_vec.len() > idx {
Ok(self.lookup_vec[idx].borrow())
} else {
Err(ErrorKind::NoSuchSymbol.into())
}
}
}
impl<'a, T: ?Sized, I> ResolveUnchecked for &'a Pool<T, I>
where T: ToOwned + Eq + Hash,
T::Owned: Eq + Hash + Borrow<T>,
I: SymbolId
{
unsafe fn resolve_unchecked(self, symbol: Self::Input) -> Self::Output {
let idx = symbol.id().to_usize().expect("Unexpected failure to convert symbol ID to usize");
self.lookup_vec.get_unchecked(idx).borrow()
}
}
#[cfg(test)]
mod tests {
use super::Pool;
use traits::*;
use ErrorKind;
#[test]
fn resolve_returns_expected_results() {
let mut p1 = Pool::<str,u16>::new();
let mut p2 = Pool::<str,u16>::new();
let s1 = p1.intern("foo").unwrap();
let s2 = p2.intern("bar").unwrap();
assert_eq!(Ok("foo"), p1.resolve(s1));
assert_eq!(Ok("bar"), p2.resolve(s2));
}
#[test]
fn has_expected_len_and_capacity() {
let mut pool = Pool::<u16,u8>::new();
assert!(pool.is_empty());
for i in 0u16..200 {
pool.intern(&i).expect("failed to intern value");
}
assert_eq!(200, pool.len());
assert!(! pool.is_full());
for i in 150u16..250 {
pool.intern(&i).expect("failed to intern value");
}
assert_eq!(250, pool.len());
assert!(! pool.is_full());
for i in 250u16..256 {
pool.intern(&i).expect("failed to intern value");
}
assert_eq!(256, pool.len());
assert!(pool.is_full());
pool.intern(&123).expect("failed to intern previously-interned value");
match pool.intern(&456) {
Ok(_) => panic!("unexpected `Ok` when interning unseen value in full pool"),
Err(e) => assert_eq!(ErrorKind::PoolOverflow, e.kind()),
}
}
}