use std::hash::{Hash, Hasher};
use fnv::{FnvBuildHasher, FnvHasher};
use indexmap::{Equivalent, IndexMap};
use super::ElementProperty;
#[derive(Clone, Default)]
pub struct ValueMap {
entries: IndexMap<ElementProperty, ElementProperty, FnvBuildHasher>,
}
struct StrKey<'a>(&'a str);
impl Hash for StrKey<'_> {
fn hash<H: Hasher>(&self, state: &mut H) {
ElementProperty::hash_string_key(self.0, state);
}
}
impl Equivalent<ElementProperty> for StrKey<'_> {
fn equivalent(&self, key: &ElementProperty) -> bool {
matches!(key, ElementProperty::String(s) if s.as_str() == self.0)
}
}
impl ValueMap {
pub fn new() -> Self {
Self::default()
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
entries: IndexMap::with_capacity_and_hasher(capacity, FnvBuildHasher::default()),
}
}
pub fn len(&self) -> usize {
self.entries.len()
}
pub fn is_empty(&self) -> bool {
self.entries.is_empty()
}
pub fn get(&self, key: &ElementProperty) -> Option<&ElementProperty> {
self.entries.get(key)
}
pub fn get_str(&self, key: &str) -> Option<&ElementProperty> {
self.entries.get(&StrKey(key))
}
pub fn insert(
&mut self,
key: ElementProperty,
value: ElementProperty,
) -> Option<ElementProperty> {
self.entries.insert(key, value)
}
pub fn iter(&self) -> impl Iterator<Item = (&ElementProperty, &ElementProperty)> {
self.entries.iter()
}
pub fn keys(&self) -> impl Iterator<Item = &ElementProperty> {
self.entries.keys()
}
pub fn values(&self) -> impl Iterator<Item = &ElementProperty> {
self.entries.values()
}
pub fn is_string_keyed(&self) -> bool {
self.entries
.keys()
.all(|k| matches!(k, ElementProperty::String(_)))
}
pub fn into_property(self) -> ElementProperty {
if self.is_string_keyed() {
let mut object = super::PropertyMap::with_capacity(self.entries.len());
for (key, value) in self.entries {
if let ElementProperty::String(name) = key {
object.insert(name, value);
}
}
ElementProperty::Object(Box::new(object))
} else {
ElementProperty::Map(Box::new(self))
}
}
}
impl FromIterator<(ElementProperty, ElementProperty)> for ValueMap {
fn from_iter<I: IntoIterator<Item = (ElementProperty, ElementProperty)>>(iter: I) -> Self {
Self {
entries: iter.into_iter().collect(),
}
}
}
impl IntoIterator for ValueMap {
type Item = (ElementProperty, ElementProperty);
type IntoIter = indexmap::map::IntoIter<ElementProperty, ElementProperty>;
fn into_iter(self) -> Self::IntoIter {
self.entries.into_iter()
}
}
impl<'a> IntoIterator for &'a ValueMap {
type Item = (&'a ElementProperty, &'a ElementProperty);
type IntoIter = indexmap::map::Iter<'a, ElementProperty, ElementProperty>;
fn into_iter(self) -> Self::IntoIter {
self.entries.iter()
}
}
impl PartialEq for ValueMap {
fn eq(&self, other: &Self) -> bool {
self.entries == other.entries
}
}
impl Eq for ValueMap {}
pub(crate) fn hash_entries_unordered<'a, K, V, H>(
entries: impl ExactSizeIterator<Item = (&'a K, &'a V)>,
state: &mut H,
) where
K: Hash + 'a,
V: Hash + 'a,
H: Hasher,
{
let len = entries.len();
let mut sum = 0u64;
for (k, v) in entries {
let mut entry_hasher = FnvHasher::default();
k.hash(&mut entry_hasher);
v.hash(&mut entry_hasher);
sum = sum.wrapping_add(entry_hasher.finish());
}
len.hash(state);
sum.hash(state);
}
impl Hash for ValueMap {
fn hash<H: Hasher>(&self, state: &mut H) {
hash_entries_unordered(self.entries.iter(), state);
}
}
impl std::fmt::Debug for ValueMap {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_map().entries(self.entries.iter()).finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::value::Number;
fn int(n: i64) -> ElementProperty {
ElementProperty::Number(Number::from(n))
}
fn float(n: f64) -> ElementProperty {
ElementProperty::Number(Number::from(n))
}
fn text(s: &str) -> ElementProperty {
ElementProperty::String(s.into())
}
fn hash_of<T: Hash>(v: &T) -> u64 {
let mut h = FnvHasher::default();
v.hash(&mut h);
h.finish()
}
#[test]
fn constructors_canonicalize() {
assert!(matches!(
ElementProperty::from_entries([(text("a"), int(1))]),
ElementProperty::Object(_)
));
assert!(matches!(
ElementProperty::from_entries([(int(29), int(1))]),
ElementProperty::Map(_)
));
assert!(matches!(
ElementProperty::from_entries([(text("a"), int(1)), (int(2), int(1))]),
ElementProperty::Map(_)
));
assert!(matches!(
ElementProperty::from_entries(Vec::new()),
ElementProperty::Object(_)
));
}
#[test]
fn eq_and_hash_are_order_independent() {
let a: ValueMap = [(int(1), text("x")), (text("k"), int(2))]
.into_iter()
.collect();
let b: ValueMap = [(text("k"), int(2)), (int(1), text("x"))]
.into_iter()
.collect();
assert_eq!(a, b);
assert_eq!(hash_of(&a), hash_of(&b));
let pa = a.into_property();
let pb = b.into_property();
assert_eq!(hash_of(&pa), hash_of(&pb));
assert_eq!(pa, pb);
}
#[test]
fn object_hash_is_order_independent() {
let mut x = crate::value::PropertyMap::default();
x.insert("a", int(1));
x.insert("b", int(2));
let mut y = crate::value::PropertyMap::default();
y.insert("b", int(2));
y.insert("a", int(1));
let (x, y) = (
ElementProperty::Object(Box::new(x)),
ElementProperty::Object(Box::new(y)),
);
assert_eq!(x, y);
assert_eq!(hash_of(&x), hash_of(&y));
}
#[test]
fn get_str_finds_string_keys_only() {
let m: ValueMap = [(text("name"), int(1)), (int(7), int(2))]
.into_iter()
.collect();
assert_eq!(m.get_str("name"), Some(&int(1)));
assert_eq!(m.get_str("7"), None);
assert_eq!(m.get(&int(7)), Some(&int(2)));
}
#[test]
fn key_identity_follows_java_equals() {
let m: ValueMap = [(int(1), int(1)), (float(1.0), int(2))]
.into_iter()
.collect();
assert_eq!(m.len(), 2);
let m: ValueMap = [(float(f64::NAN), int(1)), (float(f64::NAN), int(2))]
.into_iter()
.collect();
assert_eq!(m.len(), 1);
let l12 = ElementProperty::Array(vec![int(1), int(2)]);
let l21 = ElementProperty::Array(vec![int(2), int(1)]);
assert_ne!(l12, l21);
let m: ValueMap = [(l12, int(1)), (l21, int(2))].into_iter().collect();
assert_eq!(m.len(), 2);
let m: ValueMap = [(text("1"), int(1)), (int(1), int(2))]
.into_iter()
.collect();
assert_eq!(m.len(), 2);
}
}