use std::collections::HashMap;
use std::error::Error;
use std::fmt::{Display, Formatter};
use std::hash::{Hash, Hasher};
use std::rc::Rc;
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct IdentityCounterError {
message: String,
}
impl IdentityCounterError {
fn new(message: String) -> Self {
Self { message }
}
}
impl Display for IdentityCounterError {
fn fmt(&self, formatter: &mut Formatter<'_>) -> std::fmt::Result {
formatter.write_str(&self.message)
}
}
impl Error for IdentityCounterError {}
pub struct IdentityCounter<T: ?Sized> {
counted: HashMap<Option<IdentityKey<T>>, ()>,
}
impl<T: ?Sized> IdentityCounter<T> {
pub fn new(expected_max_size: i32) -> Result<Self, IdentityCounterError> {
if expected_max_size < 0 {
return Err(IdentityCounterError::new(format!(
"expectedMaxSize is negative: {expected_max_size}"
)));
}
let initial_capacity = usize::try_from(expected_max_size)
.unwrap_or_default()
.min(1_024);
Ok(Self {
counted: HashMap::with_capacity(initial_capacity),
})
}
pub fn count(&mut self, object: Option<Rc<T>>) {
self.counted.insert(object.map(IdentityKey), ());
}
#[must_use]
pub fn is_already_counted(&self, object: Option<&Rc<T>>) -> bool {
self.counted
.contains_key(&object.map(|value| IdentityKey(Rc::clone(value))))
}
}
struct IdentityKey<T: ?Sized>(Rc<T>);
impl<T: ?Sized> PartialEq for IdentityKey<T> {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl<T: ?Sized> Eq for IdentityKey<T> {}
impl<T: ?Sized> Hash for IdentityKey<T> {
fn hash<H: Hasher>(&self, state: &mut H) {
std::ptr::hash(Rc::as_ptr(&self.0), state);
}
}
#[cfg(test)]
mod tests {
use std::rc::Rc;
use super::IdentityCounter;
#[test]
fn rejects_java_identity_hash_map_capacity_boundaries() {
assert_eq!(
IdentityCounter::<String>::new(-1)
.err()
.expect("negative error")
.to_string(),
"expectedMaxSize is negative: -1"
);
assert!(IdentityCounter::<String>::new(0).is_ok());
assert!(IdentityCounter::<String>::new(i32::MAX).is_ok());
}
#[test]
fn distinguishes_equal_values_by_reference_identity() {
let first = Rc::new("same".to_owned());
let equal_but_distinct = Rc::new("same".to_owned());
let first_alias = Rc::clone(&first);
let mut counter = IdentityCounter::new(2).expect("counter");
assert!(!counter.is_already_counted(Some(&first)));
counter.count(Some(Rc::clone(&first)));
assert!(counter.is_already_counted(Some(&first)));
assert!(counter.is_already_counted(Some(&first_alias)));
assert!(!counter.is_already_counted(Some(&equal_but_distinct)));
counter.count(Some(equal_but_distinct));
counter.count(Some(first));
assert_eq!(counter.counted.len(), 2);
}
#[test]
fn counts_java_null_once_and_keeps_non_null_separate() {
let value = Rc::new("value".to_owned());
let mut counter = IdentityCounter::new(1).expect("counter");
assert!(!counter.is_already_counted(None));
counter.count(None);
counter.count(None);
assert!(counter.is_already_counted(None));
assert!(!counter.is_already_counted(Some(&value)));
counter.count(Some(Rc::clone(&value)));
assert!(counter.is_already_counted(Some(&value)));
assert_eq!(counter.counted.len(), 2);
}
}