use std::collections::BTreeMap;
use crate::bmm::core::bmm_class::BmmClass;
use crate::bmm::core::bmm_definitions::BmmDefinitionsData;
use crate::bmm::core::bmm_package::BmmPackage;
fn path_segments(path: &str) -> Vec<&str> {
path.split(BmmDefinitionsData::PACKAGE_NAME_DELIMITER)
.map(str::trim)
.filter(|segment| !segment.is_empty())
.collect()
}
pub(crate) fn package_at_path_in<'a>(
packages: Option<&'a BTreeMap<String, BmmPackage>>,
path: &str,
) -> Option<&'a BmmPackage> {
resolve_segments(packages, &path_segments(path))
}
fn resolve_segments<'a>(
packages: Option<&'a BTreeMap<String, BmmPackage>>,
segments: &[&str],
) -> Option<&'a BmmPackage> {
let packages = packages?;
if segments.is_empty() {
return None;
}
for taken in (1..=segments.len()).rev() {
let Some(prefix) = segments.get(..taken) else {
continue;
};
let key = prefix.join(BmmDefinitionsData::PACKAGE_NAME_DELIMITER);
let hit = packages
.iter()
.find(|(candidate, _)| candidate.eq_ignore_ascii_case(&key));
let Some((_, package)) = hit else {
continue;
};
match segments.get(taken..) {
None | Some([]) => return Some(package),
Some(rest) => {
if let Some(found) = resolve_segments(package.packages.as_ref(), rest) {
return Some(found);
}
}
}
}
None
}
pub(crate) fn do_recursive_packages_in(
packages: Option<&BTreeMap<String, BmmPackage>>,
action: &mut dyn FnMut(&BmmPackage),
) {
for package in packages.into_iter().flat_map(BTreeMap::values) {
action(package);
do_recursive_packages_in(package.packages.as_ref(), action);
}
}
impl BmmPackage {
#[must_use]
pub fn path(&self) -> &str {
self.name.as_str()
}
#[must_use]
pub fn root_classes(&self) -> Vec<&BmmClass> {
if let Some(classes) = &self.classes
&& !classes.is_empty()
{
return classes.iter().collect();
}
let mut out = Vec::new();
for child in self.packages.iter().flat_map(BTreeMap::values) {
out.extend(child.root_classes());
}
out
}
#[must_use]
pub fn package_at_path(&self, a_path: &str) -> Option<&Self> {
package_at_path_in(self.packages.as_ref(), a_path)
}
#[must_use]
pub fn has_package_path(&self, a_path: &str) -> bool {
self.package_at_path(a_path).is_some()
}
pub fn do_recursive_packages(&self, action: &mut dyn FnMut(&Self)) {
do_recursive_packages_in(self.packages.as_ref(), action);
}
}
#[cfg(test)]
mod tests {
use std::collections::BTreeMap;
use crate::bmm::core::bmm_class::BmmClass;
use crate::bmm::core::bmm_class::BmmClassData;
use crate::bmm::core::bmm_package::BmmPackage;
fn package(name: &str, children: Vec<BmmPackage>, classes: &[&str]) -> BmmPackage {
let mut package = BmmPackage {
documentation: None,
packages: None,
name: name.to_owned(),
classes: openehr_base::containers::present(
classes.iter().map(|c| simple_class(c)).collect(),
),
};
if !children.is_empty() {
let map: BTreeMap<String, BmmPackage> = children
.into_iter()
.map(|child| (child.name.to_uppercase(), child))
.collect();
package.packages = Some(map);
}
package
}
fn simple_class(name: &str) -> BmmClass {
BmmClass::BmmClass(BmmClassData {
documentation: None,
name: name.to_owned(),
ancestors: None,
package: BmmPackage {
documentation: None,
packages: None,
name: "org.openehr.rm".to_owned(),
classes: openehr_base::containers::present(Vec::new()),
},
properties: None,
source_schema_id: "openehr_test_1.0.0".to_owned(),
immediate_descendants: openehr_base::containers::present(Vec::new()),
is_abstract: false,
is_primitive_type: false,
is_override: false,
})
}
#[test]
fn package_at_path_matches_keys_case_insensitively() {
let root = package(
"org.openehr.rm",
vec![package("composition", Vec::new(), &["COMPOSITION"])],
&[],
);
assert!(root.has_package_path("composition"));
assert!(root.has_package_path("COMPOSITION"));
assert!(root.has_package_path("Composition"));
assert_eq!(
root.package_at_path("composition").map(BmmPackage::path),
Some("composition")
);
assert_eq!(root.package_at_path("ehr"), None);
}
#[test]
fn package_at_path_walks_nested_segments() {
let root = package(
"org.openehr.rm",
vec![package(
"data_structures",
vec![package("item_structure", Vec::new(), &["ITEM_TREE"])],
&[],
)],
&[],
);
let leaf = root
.package_at_path("data_structures.item_structure")
.expect("the nested package resolves");
assert_eq!(leaf.path(), "item_structure");
assert!(!root.has_package_path("data_structures.missing"));
}
#[test]
fn root_classes_recurse_only_to_the_first_level_of_classes() {
let deep = package("deeper", Vec::new(), &["ELEMENT"]);
let child = package("data_structures", vec![deep], &[]);
let root = package("org.openehr.rm", vec![child], &[]);
assert_eq!(
root.root_classes()
.into_iter()
.map(BmmClass::name)
.collect::<Vec<_>>(),
["ELEMENT"]
);
let with_own = package(
"org.openehr.rm",
vec![package("child", Vec::new(), &["IGNORED"])],
&["COMPOSITION"],
);
assert_eq!(
with_own
.root_classes()
.into_iter()
.map(BmmClass::name)
.collect::<Vec<_>>(),
["COMPOSITION"]
);
}
#[test]
fn do_recursive_packages_visits_every_descendant() {
let root = package(
"org.openehr.rm",
vec![
package("composition", Vec::new(), &[]),
package(
"data_structures",
vec![package("item_structure", Vec::new(), &[])],
&[],
),
],
&[],
);
let mut visited: Vec<String> = Vec::new();
root.do_recursive_packages(&mut |package| visited.push(package.name.clone()));
assert_eq!(
visited,
[
"composition".to_owned(),
"data_structures".to_owned(),
"item_structure".to_owned()
]
);
}
}