use energyml_core::identity;
use energyml_core::reflect::TypeKind;
use energyml_core::value::{FieldValue, Reflect, Scalar};
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ArrayLocation {
pub file: String,
pub path_in_file: String,
pub mime: Option<String>,
pub start: Vec<i64>,
pub count: Vec<i64>,
pub proxy_uuid: Option<String>,
pub field_path: String,
pub resource_uri: Option<String>,
}
impl ArrayLocation {
pub fn is_resolved(&self) -> bool {
!self.file.is_empty()
}
pub fn etp(resource_uri: impl Into<String>, path_in_resource: impl Into<String>) -> Self {
Self {
path_in_file: path_in_resource.into(),
resource_uri: Some(resource_uri.into()),
..Self::default()
}
}
pub fn etp_address(&self) -> Option<(&str, &str)> {
self.resource_uri
.as_deref()
.map(|uri| (uri, self.path_in_file.as_str()))
}
}
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ProxyTarget {
pub file: String,
pub mime: Option<String>,
}
impl ProxyTarget {
pub fn file(name: impl Into<String>) -> Self {
Self {
file: name.into(),
mime: None,
}
}
}
pub trait ProxyResolver {
fn resolve(&self, proxy_uuid: &str) -> Option<ProxyTarget>;
}
impl<F: Fn(&str) -> Option<ProxyTarget>> ProxyResolver for F {
fn resolve(&self, proxy_uuid: &str) -> Option<ProxyTarget> {
self(proxy_uuid)
}
}
pub struct NoProxies;
impl ProxyResolver for NoProxies {
fn resolve(&self, _proxy_uuid: &str) -> Option<ProxyTarget> {
None
}
}
pub fn locate(object: &dyn Reflect) -> Vec<ArrayLocation> {
locate_with(object, &NoProxies)
}
pub fn locate_with(object: &dyn Reflect, resolver: &dyn ProxyResolver) -> Vec<ArrayLocation> {
let mut out = Vec::new();
walk(object, String::new(), resolver, &mut out, 0);
out
}
const NEW_FORM: &str = "ExternalDataArrayPart";
const MID_FORM: &str = "ExternalDatasetPart";
const OLD_FORM: &str = "Hdf5Dataset";
const CHANNEL_DATA: &str = "ChannelData";
pub fn is_channel_data_in_file(file_uri: &str, data: &str) -> bool {
!file_uri.trim().is_empty() && data.starts_with('/')
}
const CHANNEL_DATA_MIME: &str = "application/x-hdf5";
pub fn file_field(part_type: &str) -> &'static str {
match part_type {
CHANNEL_DATA => "file_uri",
_ => "uri",
}
}
pub fn path_field(part_type: &str) -> &'static str {
match part_type {
OLD_FORM => "path_in_hdf_file",
CHANNEL_DATA => "data",
_ => "path_in_external_file",
}
}
fn walk(
object: &dyn Reflect,
path: String,
resolver: &dyn ProxyResolver,
out: &mut Vec<ArrayLocation>,
depth: u32,
) {
if depth > 64 {
return;
}
let info = object.type_info();
match info.name {
NEW_FORM => {
out.push(from_new_form(object, &path));
return;
}
MID_FORM => {
out.push(from_mid_form(object, &path, resolver));
return;
}
OLD_FORM => {
out.push(from_old_form(object, &path, resolver));
return;
}
CHANNEL_DATA => {
if let Some(found) = from_channel_data(object, &path) {
out.push(found);
}
return;
}
_ => {}
}
for field in info.fields {
let Some(value) = object.field(field.name) else {
continue;
};
let child_path = if path.is_empty() {
field.xml_name.to_string()
} else {
format!("{path}.{}", field.xml_name)
};
walk_value(&value, child_path, resolver, out, depth);
}
}
fn walk_value(
value: &FieldValue<'_>,
path: String,
resolver: &dyn ProxyResolver,
out: &mut Vec<ArrayLocation>,
depth: u32,
) {
match value {
FieldValue::Object(child) => walk(*child, path, resolver, out, depth + 1),
FieldValue::List(items) => {
for (i, item) in items.iter().enumerate() {
walk_value(item, format!("{path}[{i}]"), resolver, out, depth);
}
}
_ => {}
}
}
fn from_channel_data(object: &dyn Reflect, path: &str) -> Option<ArrayLocation> {
let file = string_field(object, "file_uri")?;
let dataset = string_field(object, "data")?;
if !is_channel_data_in_file(&file, &dataset) {
return None;
}
Some(ArrayLocation {
file,
path_in_file: dataset,
mime: Some(CHANNEL_DATA_MIME.to_string()),
start: Vec::new(),
count: Vec::new(),
proxy_uuid: None,
field_path: path.to_string(),
resource_uri: None,
})
}
fn from_new_form(object: &dyn Reflect, path: &str) -> ArrayLocation {
ArrayLocation {
file: string_field(object, "uri").unwrap_or_default(),
path_in_file: string_field(object, "path_in_external_file").unwrap_or_default(),
mime: string_field(object, "mime_type"),
start: int_list(object, "start_index"),
count: int_list(object, "count"),
proxy_uuid: None,
field_path: path.to_string(),
resource_uri: None,
}
}
fn from_mid_form(object: &dyn Reflect, path: &str, resolver: &dyn ProxyResolver) -> ArrayLocation {
let proxy_uuid = object
.field("epc_external_part_reference")
.and_then(|value| match value {
FieldValue::Object(dor) => identity::uuid_of_reflect(dor).map(str::to_string),
_ => None,
})
.filter(|uuid| !uuid.is_empty());
let target = proxy_uuid
.as_deref()
.and_then(|uuid| resolver.resolve(uuid))
.unwrap_or_default();
ArrayLocation {
file: target.file,
path_in_file: string_field(object, "path_in_external_file").unwrap_or_default(),
mime: target.mime,
start: Vec::new(),
count: Vec::new(),
proxy_uuid,
field_path: path.to_string(),
resource_uri: None,
}
}
fn from_old_form(object: &dyn Reflect, path: &str, resolver: &dyn ProxyResolver) -> ArrayLocation {
let proxy_uuid = object
.field("hdf_proxy")
.and_then(|value| match value {
FieldValue::Object(dor) => identity::uuid_of_reflect(dor).map(str::to_string),
_ => None,
})
.filter(|uuid| !uuid.is_empty());
let target = proxy_uuid
.as_deref()
.and_then(|uuid| resolver.resolve(uuid))
.unwrap_or_default();
ArrayLocation {
file: target.file,
path_in_file: string_field(object, "path_in_hdf_file").unwrap_or_default(),
mime: target.mime,
start: Vec::new(),
count: Vec::new(),
proxy_uuid,
field_path: path.to_string(),
resource_uri: None,
}
}
fn string_field(object: &dyn Reflect, name: &str) -> Option<String> {
match object.field(name)? {
FieldValue::Scalar(Scalar::Str(s)) if !s.is_empty() => Some(s.to_string()),
_ => None,
}
}
fn int_list(object: &dyn Reflect, name: &str) -> Vec<i64> {
let Some(value) = object.field(name) else {
return Vec::new();
};
let one = |v: &FieldValue<'_>| match v {
FieldValue::Scalar(Scalar::Int(i)) => Some(*i),
FieldValue::Scalar(Scalar::UInt(u)) => i64::try_from(*u).ok(),
_ => None,
};
match value {
FieldValue::List(items) => items.iter().filter_map(one).collect(),
other => one(&other).into_iter().collect(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ByteOrder {
LittleEndian,
BigEndian,
}
impl std::fmt::Display for ByteOrder {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str(match self {
ByteOrder::LittleEndian => "little-endian",
ByteOrder::BigEndian => "big-endian",
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ArrayMetadata {
pub dimensions: Vec<i64>,
pub element_type: String,
pub logical_type: Option<String>,
pub preferred_subarray_dimensions: Vec<i64>,
pub store_created: Option<i64>,
pub store_last_write: Option<i64>,
}
impl ArrayMetadata {
pub fn new(dimensions: Vec<i64>, element_type: impl Into<String>) -> Self {
Self {
dimensions,
element_type: element_type.into(),
logical_type: None,
preferred_subarray_dimensions: Vec::new(),
store_created: None,
store_last_write: None,
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum ArrayValues {
F64(Vec<f64>),
F32(Vec<f32>),
I64(Vec<i64>),
I32(Vec<i32>),
I16(Vec<i16>),
I8(Vec<i8>),
U64(Vec<u64>),
U32(Vec<u32>),
U16(Vec<u16>),
U8(Vec<u8>),
}
impl ArrayValues {
pub fn len(&self) -> usize {
match self {
Self::F64(v) => v.len(),
Self::F32(v) => v.len(),
Self::I64(v) => v.len(),
Self::I32(v) => v.len(),
Self::I16(v) => v.len(),
Self::I8(v) => v.len(),
Self::U64(v) => v.len(),
Self::U32(v) => v.len(),
Self::U16(v) => v.len(),
Self::U8(v) => v.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn to_f64(&self) -> Vec<f64> {
match self {
ArrayValues::F64(v) => v.clone(),
ArrayValues::F32(v) => v.iter().map(|x| *x as f64).collect(),
ArrayValues::I64(v) => v.iter().map(|x| *x as f64).collect(),
ArrayValues::I32(v) => v.iter().map(|x| *x as f64).collect(),
ArrayValues::I16(v) => v.iter().map(|x| *x as f64).collect(),
ArrayValues::I8(v) => v.iter().map(|x| *x as f64).collect(),
ArrayValues::U64(v) => v.iter().map(|x| *x as f64).collect(),
ArrayValues::U32(v) => v.iter().map(|x| *x as f64).collect(),
ArrayValues::U16(v) => v.iter().map(|x| *x as f64).collect(),
ArrayValues::U8(v) => v.iter().map(|x| *x as f64).collect(),
}
}
pub fn element_type(&self) -> &'static str {
match self {
Self::F64(_) => "double",
Self::F32(_) => "float",
Self::I64(_) => "int64",
Self::I32(_) => "int32",
Self::I16(_) => "int16",
Self::I8(_) => "int8",
Self::U64(_) => "uint64",
Self::U32(_) => "uint32",
Self::U16(_) => "uint16",
Self::U8(_) => "uint8",
}
}
pub fn as_bytes(&self) -> &[u8] {
match self {
Self::F64(v) => bytemuck::cast_slice(v),
Self::F32(v) => bytemuck::cast_slice(v),
Self::I64(v) => bytemuck::cast_slice(v),
Self::I32(v) => bytemuck::cast_slice(v),
Self::I16(v) => bytemuck::cast_slice(v),
Self::I8(v) => bytemuck::cast_slice(v),
Self::U64(v) => bytemuck::cast_slice(v),
Self::U32(v) => bytemuck::cast_slice(v),
Self::U16(v) => bytemuck::cast_slice(v),
Self::U8(v) => v,
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct ArrayData {
pub metadata: ArrayMetadata,
pub values: ArrayValues,
}
impl ArrayData {
pub fn element_count(&self) -> usize {
self.metadata.dimensions.iter().product::<i64>().max(0) as usize
}
pub fn as_bytes(&self) -> &[u8] {
self.values.as_bytes()
}
pub fn f64(&self) -> Option<&[f64]> {
match &self.values {
ArrayValues::F64(v) => Some(v),
_ => None,
}
}
pub fn f32(&self) -> Option<&[f32]> {
match &self.values {
ArrayValues::F32(v) => Some(v),
_ => None,
}
}
pub fn i64(&self) -> Option<&[i64]> {
match &self.values {
ArrayValues::I64(v) => Some(v),
_ => None,
}
}
pub fn u32(&self) -> Option<&[u32]> {
match &self.values {
ArrayValues::U32(v) => Some(v),
_ => None,
}
}
pub fn to_f64(&self) -> Vec<f64> {
self.values.to_f64()
}
}
#[non_exhaustive]
pub enum ArrayBuffer<'a> {
F64(&'a mut [f64]),
F32(&'a mut [f32]),
I64(&'a mut [i64]),
I32(&'a mut [i32]),
U64(&'a mut [u64]),
U32(&'a mut [u32]),
U8(&'a mut [u8]),
}
impl ArrayBuffer<'_> {
pub fn len(&self) -> usize {
match self {
Self::F64(b) => b.len(),
Self::F32(b) => b.len(),
Self::I64(b) => b.len(),
Self::I32(b) => b.len(),
Self::U64(b) => b.len(),
Self::U32(b) => b.len(),
Self::U8(b) => b.len(),
}
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn element_type(&self) -> &'static str {
match self {
Self::F64(_) => "double",
Self::F32(_) => "float",
Self::I64(_) => "int64",
Self::I32(_) => "int32",
Self::U64(_) => "uint64",
Self::U32(_) => "uint32",
Self::U8(_) => "uint8",
}
}
}
pub fn is_array_pointer(info: &energyml_core::reflect::TypeInfo) -> bool {
info.kind == TypeKind::Struct && (info.name == NEW_FORM || info.name == OLD_FORM)
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Resolution {
pub declared: String,
pub path: std::path::PathBuf,
pub origin: crate::backend::Origin,
}
impl Resolution {
pub fn name(&self) -> String {
self.path
.file_name()
.map(|n| n.to_string_lossy().into_owned())
.unwrap_or_else(|| self.path.display().to_string())
}
pub fn agrees(&self) -> bool {
!self.origin.is_a_departure()
}
}