use std::marker::PhantomData;
use crate::format::messages::attribute::AttributeMessage;
use crate::format::messages::datatype::DatatypeMessage;
use crate::error::{Hdf5Error, Result};
use crate::file::{borrow_inner, borrow_inner_mut, clone_inner, H5FileInner, SharedInner};
use crate::types::VarLenUnicode;
pub struct H5Attribute {
file_inner: SharedInner,
ds_index: usize,
name: String,
write_dims: Vec<usize>,
read_attr: Option<AttributeMessage>,
}
impl H5Attribute {
pub(crate) fn new_reader(file_inner: SharedInner, attr_msg: AttributeMessage) -> Self {
Self {
file_inner,
ds_index: usize::MAX,
name: attr_msg.name.clone(),
write_dims: Vec::new(),
read_attr: Some(attr_msg),
}
}
pub fn name(&self) -> &str {
&self.name
}
pub fn write_scalar(&self, value: &VarLenUnicode) -> Result<()> {
let inner = borrow_inner(&self.file_inner);
match &*inner {
H5FileInner::Writer(writer) => {
let attr_msg = writer.vlen_string_attribute(&self.name, &value.0)?;
writer.add_dataset_attribute(self.ds_index, attr_msg)?;
Ok(())
}
H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
"cannot write attributes in read mode".into(),
)),
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn write_string(&self, value: &str) -> Result<()> {
self.write_scalar(&VarLenUnicode(value.to_string()))
}
pub fn write_string_array(&self, values: &[&str]) -> Result<()> {
if self.write_dims.len() > 1 {
return Err(Hdf5Error::InvalidState(format!(
"attribute '{}' string arrays support only a 1-D shape, got {:?}",
self.name, self.write_dims
)));
}
let expected: usize = self.write_dims.iter().product();
if values.len() != expected {
return Err(Hdf5Error::InvalidState(format!(
"attribute '{}' shape {:?} needs {} elements, got {}",
self.name,
self.write_dims,
expected,
values.len()
)));
}
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Writer(writer) => {
let attr_msg = writer.vlen_string_array_attribute(&self.name, values)?;
writer.add_dataset_attribute(self.ds_index, attr_msg)?;
Ok(())
}
H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
"cannot write attributes in read mode".into(),
)),
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn write_numeric<T: crate::types::H5Type>(&self, value: &T) -> Result<()> {
let es = T::element_size();
let raw = unsafe { std::slice::from_raw_parts(value as *const T as *const u8, es) };
let attr_msg = AttributeMessage::scalar_numeric(&self.name, T::hdf5_type(), raw.to_vec());
let inner = borrow_inner(&self.file_inner);
match &*inner {
H5FileInner::Writer(writer) => {
writer.add_dataset_attribute(self.ds_index, attr_msg)?;
Ok(())
}
H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
"cannot write attributes in read mode".into(),
)),
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn write_array<T: crate::types::H5Type>(&self, values: &[T]) -> Result<()> {
let expected: usize = self.write_dims.iter().product();
if values.len() != expected {
return Err(Hdf5Error::InvalidState(format!(
"attribute '{}' shape {:?} needs {} elements, got {}",
self.name,
self.write_dims,
expected,
values.len()
)));
}
let es = T::element_size();
let raw =
unsafe { std::slice::from_raw_parts(values.as_ptr() as *const u8, values.len() * es) };
let dims_u64: Vec<u64> = self.write_dims.iter().map(|&d| d as u64).collect();
let attr_msg =
AttributeMessage::array_numeric(&self.name, T::hdf5_type(), &dims_u64, raw.to_vec());
let inner = borrow_inner(&self.file_inner);
match &*inner {
H5FileInner::Writer(writer) => {
writer.add_dataset_attribute(self.ds_index, attr_msg)?;
Ok(())
}
H5FileInner::Reader(_) => Err(Hdf5Error::InvalidState(
"cannot write attributes in read mode".into(),
)),
H5FileInner::Closed => Err(Hdf5Error::InvalidState("file is closed".into())),
}
}
pub fn read_numeric<T: crate::types::H5Type>(&self) -> Result<T> {
let data = self
.read_attr
.as_ref()
.map(|a| &a.data)
.ok_or_else(|| Hdf5Error::InvalidState("attribute has no read data".into()))?;
let es = T::element_size();
if data.len() < es {
return Err(Hdf5Error::TypeMismatch(format!(
"attribute data {} bytes, need {} for type",
data.len(),
es
)));
}
unsafe {
let mut val = std::mem::MaybeUninit::<T>::uninit();
std::ptr::copy_nonoverlapping(data.as_ptr(), val.as_mut_ptr() as *mut u8, es);
Ok(val.assume_init())
}
}
pub fn read_string(&self) -> Result<String> {
let attr = self.read_attr.as_ref().ok_or_else(|| {
Hdf5Error::InvalidState("attribute has no read data (write-mode handle?)".into())
})?;
let mut inner = borrow_inner_mut(&self.file_inner);
match &mut *inner {
H5FileInner::Reader(reader) => Ok(reader.attr_string_value(attr)?),
_ => {
let end = attr
.data
.iter()
.position(|&b| b == 0)
.unwrap_or(attr.data.len());
Ok(String::from_utf8_lossy(&attr.data[..end]).to_string())
}
}
}
pub fn datatype(&self) -> Result<DatatypeMessage> {
self.read_attr
.as_ref()
.map(|a| a.datatype.clone())
.ok_or_else(|| {
Hdf5Error::InvalidState("attribute has no read data (write-mode handle?)".into())
})
}
pub fn read_raw(&self) -> Result<Vec<u8>> {
self.read_attr
.as_ref()
.map(|a| a.data.clone())
.ok_or_else(|| {
Hdf5Error::InvalidState("attribute has no read data (write-mode handle?)".into())
})
}
}
pub trait AttrShape {
fn attr_dims(&self) -> Vec<usize>;
}
impl AttrShape for () {
fn attr_dims(&self) -> Vec<usize> {
Vec::new()
}
}
impl AttrShape for &[usize] {
fn attr_dims(&self) -> Vec<usize> {
self.to_vec()
}
}
impl<const N: usize> AttrShape for [usize; N] {
fn attr_dims(&self) -> Vec<usize> {
self.to_vec()
}
}
impl<const N: usize> AttrShape for &[usize; N] {
fn attr_dims(&self) -> Vec<usize> {
self.to_vec()
}
}
impl AttrShape for Vec<usize> {
fn attr_dims(&self) -> Vec<usize> {
self.clone()
}
}
pub struct AttrBuilder<'a, T> {
file_inner: &'a SharedInner,
ds_index: usize,
dims: Vec<usize>,
_marker: PhantomData<T>,
}
impl<'a, T> AttrBuilder<'a, T> {
pub(crate) fn new(file_inner: &'a SharedInner, ds_index: usize) -> Self {
Self {
file_inner,
ds_index,
dims: Vec::new(),
_marker: PhantomData,
}
}
#[must_use]
pub fn shape<S: AttrShape>(mut self, shape: S) -> Self {
self.dims = shape.attr_dims();
self
}
pub fn create(self, name: &str) -> Result<H5Attribute> {
Ok(H5Attribute {
file_inner: clone_inner(self.file_inner),
ds_index: self.ds_index,
name: name.to_string(),
write_dims: self.dims,
read_attr: None,
})
}
}