use std::{hash::Hash, io};
use crate::{
property::Kind,
stream::{layout::Numbering, owned},
traits::{PropertyExt, PropertyValueExt, ReadProperty, WriteProperty, WriterExt},
Error, PropertyValueEnum, ValueSlot,
};
use byteorder::{WriteBytesExt, LE};
use ltk_hash::HashWidth;
use ltk_io_ext::{measure, window_at};
use super::container::{check_hash_width, mixed_widths, track_hash_width};
impl Hash for PropertyValueEnum {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
match self {
PropertyValueEnum::None(i) => i.hash(state),
PropertyValueEnum::Bool(i) => i.hash(state),
PropertyValueEnum::I8(i) => i.hash(state),
PropertyValueEnum::U8(i) => i.hash(state),
PropertyValueEnum::I16(i) => i.hash(state),
PropertyValueEnum::U16(i) => i.hash(state),
PropertyValueEnum::I32(i) => i.hash(state),
PropertyValueEnum::U32(i) => i.hash(state),
PropertyValueEnum::I64(i) => i.hash(state),
PropertyValueEnum::U64(i) => i.hash(state),
PropertyValueEnum::BitBool(i) => i.hash(state),
_ => std::mem::discriminant(self).hash(state),
}
}
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, PartialEq, Debug, Default)]
pub struct Map {
key_kind: Kind,
value_kind: Kind,
entries: Vec<(PropertyValueEnum, PropertyValueEnum)>,
}
impl Map {
#[inline(always)]
#[must_use]
pub fn key_kind(&self) -> Kind {
self.key_kind
}
#[inline(always)]
#[must_use]
pub fn value_kind(&self) -> Kind {
self.value_kind
}
#[inline(always)]
#[must_use]
pub fn entries(&self) -> &[(PropertyValueEnum, PropertyValueEnum)] {
&self.entries
}
#[must_use]
pub fn key_hash_width(&self) -> Option<HashWidth> {
match self.entries.first() {
Some((PropertyValueEnum::Hash(key), _)) => Some(key.width()),
_ => None,
}
}
#[must_use]
pub fn value_hash_width(&self) -> Option<HashWidth> {
match self.entries.first() {
Some((_, PropertyValueEnum::Hash(value))) => Some(value.width()),
_ => None,
}
}
#[inline(always)]
#[must_use]
pub fn slot(&mut self, index: usize) -> Option<ValueSlot<'_>> {
let value_kind = self.value_kind;
let (_, value) = self.entries.get_mut(index)?;
Some(ValueSlot::pinned(value_kind, value))
}
pub(crate) fn entries_mut(
&mut self,
) -> impl Iterator<Item = (&PropertyValueEnum, &mut PropertyValueEnum)> {
self.entries.iter_mut().map(|(key, value)| (&*key, value))
}
#[inline(always)]
#[must_use]
pub fn into_entries(self) -> Vec<(PropertyValueEnum, PropertyValueEnum)> {
self.entries
}
#[inline(always)]
pub fn push(&mut self, key: PropertyValueEnum, value: PropertyValueEnum) -> Result<(), Error> {
if self.key_kind != key.kind() {
return Err(Error::MismatchedContainerTypes {
expected: self.key_kind,
got: key.kind(),
});
}
if self.value_kind != value.kind() {
return Err(Error::MismatchedContainerTypes {
expected: self.value_kind,
got: value.kind(),
});
}
check_hash_width(self.key_hash_width(), &key)?;
check_hash_width(self.value_hash_width(), &value)?;
self.entries.push((key, value));
Ok(())
}
pub fn empty(key_kind: Kind, value_kind: Kind) -> Result<Self, Error> {
Self::new(key_kind, value_kind, Vec::new())
}
pub fn new(
key_kind: Kind,
value_kind: Kind,
entries: Vec<(PropertyValueEnum, PropertyValueEnum)>,
) -> Result<Self, Error> {
if !key_kind.is_valid_map_key() {
return Err(Error::InvalidKeyType(key_kind));
}
if value_kind.is_container() {
return Err(Error::InvalidNesting(value_kind));
}
let (mut key_hash_width, mut value_hash_width) = (None, None);
for (k, v) in &entries {
if k.kind() != key_kind {
return Err(Error::MismatchedContainerTypes {
expected: key_kind,
got: k.kind(),
});
}
if v.kind() != value_kind {
return Err(Error::MismatchedContainerTypes {
expected: value_kind,
got: v.kind(),
});
}
track_hash_width(&mut key_hash_width, k)?;
track_hash_width(&mut value_hash_width, v)?;
}
Ok(Self {
key_kind,
value_kind,
entries,
})
}
}
impl PropertyValueExt for Map {
const KIND: Kind = Kind::Map;
}
impl PropertyExt for Map {
fn size_no_header(&self) -> usize {
1 + 1
+ 4
+ 4
+ self
.entries
.iter()
.map(|(k, v)| k.size_no_header() + v.size_no_header())
.sum::<usize>()
}
}
impl ReadProperty for Map {
fn from_reader<R: io::Read + io::Seek + ?Sized>(
reader: &mut R,
legacy: bool,
) -> Result<Self, Error> {
owned::read_from(
reader,
Kind::Map,
Numbering::from_legacy(legacy),
owned::read_map,
)
}
}
impl WriteProperty for Map {
fn to_writer<R: io::Write + io::Seek + ?Sized>(
&self,
writer: &mut R,
legacy: bool,
) -> Result<(), io::Error> {
if legacy {
unimplemented!("legacy map writing")
}
writer.write_property_kind(self.key_kind)?;
writer.write_property_kind(self.value_kind)?;
let size_pos = writer.stream_position()?;
writer.write_u32::<LE>(0)?;
let (size, _) = measure(writer, |writer| {
writer.write_u32::<LE>(self.entries.len() as _)?;
let (mut key_hash_width, mut value_hash_width) = (None, None);
for (k, v) in self.entries.iter() {
track_hash_width(&mut key_hash_width, k).map_err(mixed_widths)?;
track_hash_width(&mut value_hash_width, v).map_err(mixed_widths)?;
k.to_writer(writer)?;
v.to_writer(writer)?;
}
Ok::<_, io::Error>(())
})?;
window_at(writer, size_pos, |writer| writer.write_u32::<LE>(size as _))?;
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::property::values;
#[test]
fn rejects_kinds_its_own_reader_would_refuse() {
for kind in [
Kind::Container,
Kind::UnorderedContainer,
Kind::Optional,
Kind::Map,
] {
assert!(matches!(
Map::empty(kind, Kind::I32),
Err(Error::InvalidKeyType(k)) if k == kind
));
assert!(matches!(
Map::empty(Kind::U32, kind),
Err(Error::InvalidNesting(k)) if k == kind
));
}
for kind in [
Kind::Struct,
Kind::Embedded,
Kind::BitBool,
Kind::ObjectLink,
] {
assert!(matches!(
Map::empty(kind, Kind::I32),
Err(Error::InvalidKeyType(k)) if k == kind
));
assert!(Map::empty(Kind::Hash, kind).is_ok());
}
assert!(Map::empty(Kind::WadChunkLink, Kind::I32).is_ok());
}
#[test]
fn rejects_an_entry_that_does_not_match_its_kinds() {
assert!(matches!(
Map::new(
Kind::U32,
Kind::String,
vec![(values::I32::new(1).into(), values::String::from("a").into())],
),
Err(Error::MismatchedContainerTypes {
expected: Kind::U32,
got: Kind::I32
})
));
let mut map = Map::empty(Kind::U32, Kind::String).unwrap();
assert!(map
.push(values::U32::new(1).into(), values::I32::new(2).into())
.is_err());
assert!(map
.push(values::U32::new(1).into(), values::String::from("a").into())
.is_ok());
assert_eq!(map.entries().len(), 1);
}
}