use std::io;
use crate::{
property::{Kind, NoMeta},
stream::{layout::Numbering, owned},
traits::{PropertyExt, PropertyValueExt, ReadProperty, WriteProperty, WriterExt},
Error, PropertyValueEnum, ValueSlot,
};
use byteorder::{WriteBytesExt, LE};
use ltk_io_ext::{measure, window_at};
mod item;
pub use item::ContainerItem;
#[cfg_attr(
feature = "serde",
derive(serde::Serialize, serde::Deserialize),
serde(bound = "for <'dee> M: serde::Serialize + serde::Deserialize<'dee>")
)]
#[derive(Clone, Debug, PartialEq)]
pub struct Container<M = NoMeta> {
item_kind: Kind,
items: Vec<PropertyValueEnum<M>>,
pub meta: M,
}
impl<M: Default> Container<M> {
pub fn empty(item_kind: Kind) -> Result<Self, Error> {
Self::new(item_kind, Vec::new())
}
pub fn new(item_kind: Kind, items: Vec<PropertyValueEnum<M>>) -> Result<Self, Error> {
if item_kind.is_container() {
return Err(Error::InvalidNesting(item_kind));
}
for item in &items {
if item.kind() != item_kind {
return Err(Error::MismatchedContainerTypes {
expected: item_kind,
got: item.kind(),
});
}
}
Ok(Self {
item_kind,
items,
meta: M::default(),
})
}
}
impl<M> Container<M> {
#[inline(always)]
#[must_use]
pub fn item_kind(&self) -> Kind {
self.item_kind
}
#[inline(always)]
#[must_use]
pub fn items(&self) -> &[PropertyValueEnum<M>] {
&self.items
}
#[inline(always)]
#[must_use]
pub fn get(&self, index: usize) -> Option<&PropertyValueEnum<M>> {
self.items.get(index)
}
#[inline(always)]
#[must_use]
pub fn slot(&mut self, index: usize) -> Option<ValueSlot<'_, M>> {
let item_kind = self.item_kind;
Some(ValueSlot::pinned(item_kind, self.items.get_mut(index)?))
}
#[inline(always)]
#[must_use]
pub fn len(&self) -> usize {
self.items.len()
}
#[inline(always)]
#[must_use]
pub fn is_empty(&self) -> bool {
self.items.is_empty()
}
pub fn push(&mut self, value: PropertyValueEnum<M>) -> Result<(), Error> {
if value.kind() != self.item_kind {
return Err(Error::MismatchedContainerTypes {
expected: self.item_kind,
got: value.kind(),
});
}
self.items.push(value);
Ok(())
}
#[inline(always)]
#[must_use]
pub fn into_items(self) -> Vec<PropertyValueEnum<M>> {
self.items
}
#[inline(always)]
#[must_use]
pub fn no_meta(self) -> Container<NoMeta> {
Container {
item_kind: self.item_kind,
items: self.items.into_iter().map(|i| i.no_meta()).collect(),
meta: NoMeta,
}
}
}
impl<M: Default> Default for Container<M> {
fn default() -> Self {
Self {
item_kind: Kind::None,
items: Vec::new(),
meta: M::default(),
}
}
}
impl<M: Default, T: ContainerItem + Into<PropertyValueEnum<M>>> From<Vec<T>> for Container<M> {
fn from(items: Vec<T>) -> Self {
items.into_iter().collect()
}
}
impl<M: Default, T: ContainerItem + Into<PropertyValueEnum<M>>> FromIterator<T> for Container<M> {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
Self {
item_kind: T::KIND,
items: iter.into_iter().map(Into::into).collect(),
meta: M::default(),
}
}
}
impl<M: Default> TryFrom<Vec<PropertyValueEnum<M>>> for Container<M> {
type Error = Error;
fn try_from(items: Vec<PropertyValueEnum<M>>) -> Result<Self, Self::Error> {
let item_kind = items.first().ok_or(Error::EmptyContainer)?.kind();
Self::new(item_kind, items)
}
}
impl<M> PropertyExt for Container<M> {
fn size_no_header(&self) -> usize {
9 + self.items.iter().map(|i| i.size_no_header()).sum::<usize>()
}
type Meta = M;
fn meta(&self) -> &Self::Meta {
&self.meta
}
fn meta_mut(&mut self) -> &mut Self::Meta {
&mut self.meta
}
}
impl<M> PropertyValueExt for Container<M> {
const KIND: Kind = Kind::Container;
}
impl<M: Default> ReadProperty for Container<M> {
fn from_reader<R: io::Read + io::Seek + ?Sized>(
reader: &mut R,
legacy: bool,
) -> Result<Self, Error> {
owned::read_from(
reader,
Kind::Container,
Numbering::from_legacy(legacy),
owned::read_container,
)
}
}
impl<M: Clone> WriteProperty for Container<M> {
fn to_writer<R: io::Write + io::Seek + ?Sized>(
&self,
writer: &mut R,
legacy: bool,
) -> Result<(), io::Error> {
if legacy {
unimplemented!("legacy container writing");
}
writer.write_property_kind(self.item_kind)?;
let size_pos = writer.stream_position()?;
writer.write_u32::<LE>(0)?;
let (size, _) = measure(writer, |writer| {
writer.write_u32::<LE>(self.items.len() as _)?;
for item in &self.items {
item.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 takes_its_item_kind_from_the_type() {
let list = Container::<NoMeta>::from(vec![values::I32::new(1), values::I32::new(2)]);
assert_eq!(list.item_kind(), Kind::I32);
assert_eq!(list.len(), 2);
let list: Container = std::iter::empty::<values::String>().collect();
assert_eq!(list.item_kind(), Kind::String);
assert!(list.is_empty());
}
#[test]
fn infers_its_item_kind_from_the_first_item() {
let items: Vec<PropertyValueEnum> =
vec![values::U8::new(1).into(), values::U8::new(2).into()];
let list: Container = Container::try_from(items).unwrap();
assert_eq!(list.item_kind(), Kind::U8);
let empty: Result<Container, _> = Container::try_from(Vec::<PropertyValueEnum>::new());
assert!(matches!(empty, Err(Error::EmptyContainer)));
}
#[test]
fn rejects_an_item_that_is_not_its_item_kind() {
assert!(matches!(
Container::<NoMeta>::new(Kind::I32, vec![values::String::from("no").into()]),
Err(Error::MismatchedContainerTypes {
expected: Kind::I32,
got: Kind::String
})
));
let mut list = Container::<NoMeta>::empty(Kind::I32).unwrap();
assert!(matches!(
list.push(values::String::from("no").into()),
Err(Error::MismatchedContainerTypes {
expected: Kind::I32,
got: Kind::String
})
));
assert!(list.push(values::I32::new(1).into()).is_ok());
assert_eq!(list.len(), 1);
}
#[test]
fn rejects_a_nested_container() {
for kind in [
Kind::Container,
Kind::UnorderedContainer,
Kind::Optional,
Kind::Map,
] {
assert!(matches!(
Container::<NoMeta>::empty(kind),
Err(Error::InvalidNesting(k)) if k == kind
));
}
}
#[test]
fn borrows_its_items() {
let list = Container::<NoMeta>::from(vec![values::I32::new(1), values::I32::new(2)]);
assert_eq!(list.get(0), Some(&values::I32::new(1).into()));
assert_eq!(list.get(2), None);
assert_eq!(list.items().len(), 2);
assert_eq!(list.into_items().len(), 2);
}
#[test]
fn pins_its_item_kind_to_the_slots_it_hands_out() {
use crate::property::ValueMut;
let mut list = Container::<NoMeta>::from(vec![values::I32::new(1), values::I32::new(2)]);
assert!(list.slot(2).is_none());
let mut slot = list.slot(1).unwrap();
assert_eq!(slot.pinned_kind(), Some(Kind::I32));
assert!(matches!(
slot.set(values::String::from("no").into()),
Err(Error::MismatchedContainerTypes {
expected: Kind::I32,
got: Kind::String
})
));
assert_eq!(
slot.set(values::I32::new(7).into()).unwrap(),
values::I32::new(2).into()
);
let ValueMut::I32(item) = slot.as_mut() else {
panic!("the slot holds an i32");
};
item.value += 1;
assert_eq!(list.item_kind(), Kind::I32);
assert_eq!(
list.items(),
[values::I32::new(1).into(), values::I32::new(8).into()]
);
}
}