use std::fmt::{self, Debug};
use std::ops::Deref;
use hdf5_sys::h5a::{H5A_info_t, H5Acreate2, H5Adelete, H5Aget_create_plist, H5Aget_name};
use ndarray::ArrayView;
use crate::hl::plist::attribute_create::{AttributeCreate, AttributeCreateBuilder, CharEncoding};
use crate::internal_prelude::*;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct AttrInfo {
pub creation_order: Option<u32>,
pub char_encoding: CharEncoding,
pub data_size: u64,
}
impl From<&H5A_info_t> for AttrInfo {
fn from(info: &H5A_info_t) -> Self {
let creation_order = if info.corder_valid == 1 { Some(info.corder) } else { None };
let char_encoding = CharEncoding::try_from(info.cset).unwrap_or(CharEncoding::Ascii);
Self { creation_order, char_encoding, data_size: info.data_size }
}
}
#[repr(transparent)]
#[derive(Clone)]
pub struct Attribute(Handle);
impl ObjectClass for Attribute {
const NAME: &'static str = "attribute";
const VALID_TYPES: &'static [H5I_type_t] = &[H5I_ATTR];
fn from_handle(handle: Handle) -> Self {
Self(handle)
}
fn handle(&self) -> &Handle {
&self.0
}
}
impl Debug for Attribute {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
self.debug_fmt(f)
}
}
impl Deref for Attribute {
type Target = Container;
fn deref(&self) -> &Container {
unsafe { self.transmute() }
}
}
impl Attribute {
pub fn create_plist(&self) -> Result<AttributeCreate> {
h5lock!(AttributeCreate::from_id(h5try!(H5Aget_create_plist(self.id()))))
}
pub fn acpl(&self) -> Result<AttributeCreate> {
self.create_plist()
}
pub fn name(&self) -> String {
h5lock!(get_h5_str(|m, s| H5Aget_name(self.id(), s, m)).unwrap_or_else(|_| String::new()))
}
}
#[derive(Clone)]
pub struct AttributeBuilder {
builder: AttributeBuilderInner,
}
impl AttributeBuilder {
pub fn new(parent: &Location) -> Self {
Self { builder: AttributeBuilderInner::new(parent) }
}
pub fn empty<T: H5Type>(self) -> AttributeBuilderEmpty {
self.empty_as(&T::type_descriptor())
}
pub fn empty_as(self, dtype: impl Into<DatasetType>) -> AttributeBuilderEmpty {
AttributeBuilderEmpty { builder: self.builder, dtype: dtype.into() }
}
pub fn with_data<'d, A, T, D>(self, data: A) -> AttributeBuilderData<'d, T, D>
where
A: Into<ArrayView<'d, T, D>>,
T: H5Type,
D: ndarray::Dimension,
{
self.with_data_as::<A, T, D>(data, T::type_descriptor())
}
pub fn with_data_as<'d, A, T, D>(
self, data: A, dtype: impl Into<DatasetType>,
) -> AttributeBuilderData<'d, T, D>
where
A: Into<ArrayView<'d, T, D>>,
T: H5Type,
D: ndarray::Dimension,
{
AttributeBuilderData {
builder: self.builder,
data: data.into(),
dtype: dtype.into(),
conv: Conversion::Soft,
}
}
#[inline]
#[must_use]
pub fn packed(mut self, packed: bool) -> Self {
self.builder.packed(packed);
self
}
#[inline]
#[must_use]
pub fn char_encoding(mut self, encoding: CharEncoding) -> Self {
self.builder.char_encoding(encoding);
self
}
}
#[derive(Clone)]
pub struct AttributeBuilderEmpty {
builder: AttributeBuilderInner,
dtype: DatasetType,
}
impl AttributeBuilderEmpty {
pub fn shape<S: Into<Extents>>(self, extents: S) -> AttributeBuilderEmptyShape {
AttributeBuilderEmptyShape {
builder: self.builder,
dtype: self.dtype,
extents: extents.into(),
}
}
pub fn create<'n, T: Into<&'n str>>(self, name: T) -> Result<Attribute> {
self.shape(()).create(name)
}
#[inline]
#[must_use]
pub fn packed(mut self, packed: bool) -> Self {
self.builder.packed(packed);
self
}
#[inline]
#[must_use]
pub fn char_encoding(mut self, encoding: CharEncoding) -> Self {
self.builder.char_encoding(encoding);
self
}
}
#[derive(Clone)]
pub struct AttributeBuilderEmptyShape {
builder: AttributeBuilderInner,
dtype: DatasetType,
extents: Extents,
}
impl AttributeBuilderEmptyShape {
pub fn create<'n, T: Into<&'n str>>(&self, name: T) -> Result<Attribute> {
h5lock!(self.builder.create(&self.dtype, name.into(), &self.extents))
}
#[inline]
#[must_use]
pub fn packed(mut self, packed: bool) -> Self {
self.builder.packed(packed);
self
}
#[inline]
#[must_use]
pub fn char_encoding(mut self, encoding: CharEncoding) -> Self {
self.builder.char_encoding(encoding);
self
}
}
#[derive(Clone)]
pub struct AttributeBuilderData<'d, T, D> {
builder: AttributeBuilderInner,
data: ArrayView<'d, T, D>,
dtype: DatasetType,
conv: Conversion,
}
impl<'d, T, D> AttributeBuilderData<'d, T, D>
where
T: H5Type,
D: ndarray::Dimension,
{
pub fn conversion(mut self, conv: Conversion) -> Self {
self.conv = conv;
self
}
pub fn no_convert(mut self) -> Self {
self.conv = Conversion::NoOp;
self
}
pub fn create<'n, N: Into<&'n str>>(&self, name: N) -> Result<Attribute> {
ensure!(
self.data.is_standard_layout(),
"input array is not in standard layout or is not contiguous"
); let extents = Extents::from(self.data.shape());
let name = name.into();
h5lock!({
let dtype_src = Datatype::from_type::<T>()?;
let dtype_dst = self.dtype.to_datatype()?;
dtype_src.ensure_convertible(&dtype_dst, self.conv)?;
let ds = self.builder.create(&self.dtype, name, &extents)?;
if let Err(err) = ds.write(self.data.view()) {
self.builder.try_unlink(name);
Err(err)
} else {
Ok(ds)
}
})
}
#[inline]
#[must_use]
pub fn packed(mut self, packed: bool) -> Self {
self.builder.packed(packed);
self
}
#[inline]
#[must_use]
pub fn char_encoding(mut self, encoding: CharEncoding) -> Self {
self.builder.char_encoding(encoding);
self
}
}
#[derive(Clone)]
struct AttributeBuilderInner {
parent: Result<Handle>,
packed: bool,
char_encoding: CharEncoding,
}
impl AttributeBuilderInner {
pub fn new(parent: &Location) -> Self {
Self { parent: parent.try_borrow(), packed: false, char_encoding: CharEncoding::Utf8 }
}
pub fn char_encoding(&mut self, encoding: CharEncoding) {
self.char_encoding = encoding;
}
pub fn packed(&mut self, packed: bool) {
self.packed = packed;
}
#[cfg(not(any(all(feature = "1.8.18", not(feature = "1.10.0")), feature = "1.10.1")))]
fn ensure_same_file(&self, dtype: &Datatype) -> Result<()> {
use crate::hl::location::H5O_get_info;
if !dtype.is_committed() {
return Ok(());
}
let parent = try_ref_clone!(self.parent);
let parent_file = H5O_get_info(parent.id(), false)?.fileno;
let dtype_file = H5O_get_info(dtype.id(), false)?.fileno;
ensure!(
parent_file == dtype_file,
"committed datatype is in a different file than the attribute, which this HDF5 \
version cannot store"
);
Ok(())
}
unsafe fn create(
&self, dtype: &DatasetType, name: &str, extents: &Extents,
) -> Result<Attribute> {
let datatype = match dtype {
DatasetType::Descriptor(desc) => {
let desc = if self.packed { desc.to_packed_repr() } else { desc.to_c_repr() };
Datatype::from_descriptor(&desc)?
}
DatasetType::Datatype(dtype) => {
ensure!(!self.packed, "packed layout cannot be applied to an existing datatype");
#[cfg(not(any(
all(feature = "1.8.18", not(feature = "1.10.0")),
feature = "1.10.1"
)))]
self.ensure_same_file(dtype)?;
dtype.clone()
}
};
let parent = try_ref_clone!(self.parent);
let dataspace = Dataspace::try_new(extents)?;
let acpl = AttributeCreateBuilder::new().char_encoding(self.char_encoding).finish()?;
let name = to_cstring(name)?;
Attribute::from_id(h5try!(H5Acreate2(
parent.id(),
name.as_ptr(),
datatype.id(),
dataspace.id(),
acpl.id(),
H5P_DEFAULT,
)))
}
fn try_unlink(&self, name: &str) {
let name = to_cstring(name).unwrap();
if let Ok(parent) = &self.parent {
h5lock!(H5Adelete(parent.id(), name.as_ptr()));
}
}
}
#[cfg(test)]
pub mod attribute_tests {
use crate::hl::plist::attribute_create::CharEncoding;
use crate::internal_prelude::*;
use ndarray::{Array2, arr2};
use std::str::FromStr;
use types::VarLenUnicode;
#[test]
pub fn test_shape_ndim_size() {
with_tmp_file(|file| {
let d = file.new_attr::<f32>().shape((2, 3)).create("name1").unwrap();
assert_eq!(d.shape(), vec![2, 3]);
assert_eq!(d.size(), 6);
assert_eq!(d.ndim(), 2);
assert_eq!(d.is_scalar(), false);
assert_eq!(d.name(), "name1");
let d = file.new_attr::<u8>().shape(()).create("name2").unwrap();
assert_eq!(d.shape(), vec![]);
assert_eq!(d.size(), 1);
assert_eq!(d.ndim(), 0);
assert_eq!(d.is_scalar(), true);
assert_eq!(d.name(), "name2");
})
}
#[test]
pub fn test_get_file_attr_names() {
with_tmp_file(|file| {
let _ = file.new_attr::<f32>().shape((2, 3)).create("name1").unwrap();
let _ = file.new_attr::<u8>().shape(()).create("name2").unwrap();
let attr_names = file.attr_names().unwrap();
assert_eq!(attr_names, vec!["name1".to_owned(), "name2".to_owned()]);
})
}
#[test]
pub fn test_get_dataset_attr_names() {
with_tmp_file(|file| {
let ds = file.new_dataset::<u32>().shape((10, 10)).create("d1").unwrap();
let _ = ds.new_attr::<f32>().shape((2, 3)).create("name1").unwrap();
let _ = ds.new_attr::<u8>().shape(()).create("name2").unwrap();
let attr_names = ds.attr_names().unwrap();
assert_eq!(attr_names, vec!["name1".to_owned(), "name2".to_owned()]);
})
}
#[test]
pub fn test_datatype() {
with_tmp_file(|file| {
assert_eq!(
file.new_attr::<f32>().shape(1).create("name").unwrap().dtype().unwrap(),
Datatype::from_type::<f32>().unwrap()
);
})
}
#[test]
pub fn test_read_write() {
with_tmp_file(|file| {
let arr = arr2(&[[1, 2, 3], [4, 5, 6]]);
let attr = file.new_attr::<f32>().shape((2, 3)).create("foo").unwrap();
attr.as_writer().write(&arr).unwrap();
let read_attr = file.attr("foo").unwrap();
assert_eq!(read_attr.shape(), vec![2, 3]);
let arr_dyn: Array2<_> = read_attr.as_reader().read().unwrap();
assert_eq!(arr, arr_dyn.into_dimensionality().unwrap());
})
}
#[test]
pub fn test_create() {
with_tmp_file(|file| {
let attr = file.new_attr::<u32>().shape((1, 2)).create("foo").unwrap();
assert!(attr.is_valid());
assert_eq!(attr.shape(), vec![1, 2]);
assert_eq!(attr.name(), "foo");
assert_eq!(file.attr("foo").unwrap().shape(), vec![1, 2]);
})
}
#[test]
pub fn test_char_encoding() {
with_tmp_file(|file| {
let default = file.new_attr::<u32>().shape(()).create("default").unwrap();
assert_eq!(default.acpl().unwrap().char_encoding(), CharEncoding::Utf8);
let ascii = file
.new_attr::<u32>()
.char_encoding(CharEncoding::Ascii)
.shape(())
.create("ascii")
.unwrap();
assert_eq!(ascii.acpl().unwrap().char_encoding(), CharEncoding::Ascii);
let utf8 = file
.new_attr_builder()
.with_data(&arr2(&[[1, 2]]))
.char_encoding(CharEncoding::Utf8)
.create("utf8")
.unwrap();
assert_eq!(utf8.create_plist().unwrap().char_encoding(), CharEncoding::Utf8);
})
}
#[test]
pub fn test_create_with_data() {
with_tmp_file(|file| {
let arr = arr2(&[[1, 2, 3], [4, 5, 6]]);
let attr = file.new_attr_builder().with_data(&arr).create("foo").unwrap();
assert!(attr.is_valid());
assert_eq!(attr.shape(), vec![2, 3]);
assert_eq!(attr.name(), "foo");
assert_eq!(file.attr("foo").unwrap().shape(), vec![2, 3]);
let read_attr = file.attr("foo").unwrap();
assert_eq!(read_attr.shape(), vec![2, 3]);
let arr_dyn: Array2<_> = read_attr.as_reader().read().unwrap();
assert_eq!(arr, arr_dyn.into_dimensionality().unwrap());
})
}
#[test]
pub fn test_missing() {
with_tmp_file(|file| {
let _ = file.new_attr::<u32>().shape((1, 2)).create("foo").unwrap();
let missing_result = file.attr("bar");
assert!(missing_result.is_err());
})
}
#[test]
pub fn test_write_read_str() {
with_tmp_file(|file| {
let s = VarLenUnicode::from_str("var len foo").unwrap();
let attr = file.new_attr::<VarLenUnicode>().shape(()).create("foo").unwrap();
attr.as_writer().write_scalar(&s).unwrap();
let read_attr = file.attr("foo").unwrap();
assert_eq!(read_attr.shape(), []);
let r: VarLenUnicode = read_attr.as_reader().read_scalar().unwrap();
assert_eq!(r, s);
})
}
#[test]
pub fn test_list_names() {
with_tmp_file(|file| {
let arr1 = arr2(&[[123], [456]]);
let _attr1 = file.new_attr_builder().with_data(&arr1).create("foo").unwrap();
let _attr2 = file.new_attr_builder().with_data("string").create("bar").unwrap();
let attr_names = file.attr_names().unwrap();
assert_eq!(attr_names.len(), 2);
assert!(attr_names.contains(&"foo".to_string()));
assert!(attr_names.contains(&"bar".to_string()));
})
}
#[test]
pub fn test_create_with_committed_type() -> Result<()> {
with_tmp_file(|file| {
let committed = Datatype::from_type::<u32>()?;
file.commit_datatype("inty", &committed)?;
file.new_attr_builder().empty_as(committed).create("bar")?;
Ok(())
})
}
}