use std::collections::BTreeMap;
use std::collections::BTreeSet;
use std::path::PathBuf;
use crate::v1_1::bmm::core::bmm_definitions::BmmDefinitionsData;
use crate::v1_1::bmm::core::bmm_model::BmmModel;
use crate::v1_1::bmm::rm_access::error::RmAccessError;
use crate::v1_1::bmm::rm_access::reference_model_access::ReferenceModelAccess;
use crate::v1_1::bmm::rm_access::schema_descriptor::SchemaDescriptor;
use crate::v1_1::bmm_persistence::p_bmm_schema::PBmmSchema;
use openehr_base::containers::present;
impl ReferenceModelAccess {
#[must_use]
pub fn new(schema_directories: Vec<String>) -> Self {
Self {
schema_directories: present(schema_directories),
all_schemas: None,
valid_models: None,
}
}
#[must_use]
pub fn all_schemas(&self) -> Option<&BTreeMap<String, SchemaDescriptor>> {
self.all_schemas.as_ref()
}
#[must_use]
pub fn valid_models(&self) -> Option<&BTreeMap<String, BmmModel>> {
self.valid_models.as_ref()
}
pub fn initialise_all(&mut self) -> Result<(), RmAccessError> {
let directories = self.schema_directories.clone().unwrap_or_default();
self.initialise(&directories, None)
}
pub fn initialise_with_load_list(
&mut self,
a_schema_dirs: Vec<String>,
a_schema_load_list: &[String],
) -> Result<(), RmAccessError> {
self.schema_directories = present(a_schema_dirs);
let directories = self.schema_directories.clone().unwrap_or_default();
self.initialise(&directories, Some(a_schema_load_list))
}
pub fn reload_schemas(&mut self) -> Result<(), RmAccessError> {
let selection: Vec<String> = self
.all_schemas
.iter()
.flat_map(BTreeMap::keys)
.cloned()
.collect();
let directories = self.schema_directories.clone().unwrap_or_default();
if selection.is_empty() {
return self.initialise(&directories, None);
}
self.initialise(&directories, Some(&selection))
}
fn initialise(
&mut self,
directories: &[String],
selection: Option<&[String]>,
) -> Result<(), RmAccessError> {
let mut found = scan(directories)?;
let selected = match selection {
None => found.keys().cloned().collect(),
Some(list) => closure(&found, list)?,
};
let mut all: BTreeMap<String, SchemaDescriptor> = BTreeMap::new();
for id in &selected {
let mut descriptor = found
.remove(id)
.ok_or_else(|| RmAccessError::UnknownLoadListEntry { id: id.clone() })?;
descriptor.load()?;
all.insert(id.clone(), descriptor);
}
let ids: Vec<String> = all.keys().cloned().collect();
for descriptor in all.values() {
descriptor.validate()?;
descriptor.validate_includes(&ids)?;
}
let persisted: BTreeMap<String, PBmmSchema> = all
.values()
.filter_map(|descriptor| {
descriptor
.p_schema()
.map(|schema| (descriptor.schema_id().to_owned(), schema.clone()))
})
.collect();
let top_level: Vec<String> = all
.iter()
.filter(|(_, descriptor)| descriptor.is_top_level(&all))
.map(|(id, _)| id.clone())
.collect();
let mut valid_models: BTreeMap<String, BmmModel> = BTreeMap::new();
for id in &top_level {
let Some(descriptor) = all.get_mut(id) else {
continue;
};
descriptor.create_schema(&persisted)?;
if let Some(model) = descriptor.schema() {
valid_models.insert(descriptor.logical_model_name(), model.clone());
}
}
self.all_schemas = (!all.is_empty()).then_some(all);
self.valid_models = (!valid_models.is_empty()).then_some(valid_models);
Ok(())
}
}
fn scan(directories: &[String]) -> Result<BTreeMap<String, SchemaDescriptor>, RmAccessError> {
let mut files: Vec<PathBuf> = Vec::new();
for directory in directories {
let entries = std::fs::read_dir(directory).map_err(|source| RmAccessError::Directory {
directory: directory.clone(),
source,
})?;
for entry in entries {
let entry = entry.map_err(|source| RmAccessError::Directory {
directory: directory.clone(),
source,
})?;
let path = entry.path();
let is_schema_file = path
.file_name()
.and_then(std::ffi::OsStr::to_str)
.is_some_and(|name| name.ends_with(BmmDefinitionsData::BMM_SCHEMA_FILE_EXTENSION));
if is_schema_file && path.is_file() {
files.push(path);
}
}
}
files.sort();
let mut out: BTreeMap<String, SchemaDescriptor> = BTreeMap::new();
for path in files {
let descriptor = SchemaDescriptor::from_schema_file(&path)?;
let id = descriptor.schema_id().to_owned();
if let Some(first) = out.get(&id) {
return Err(RmAccessError::DuplicateSchemaId {
id,
first: first.schema_path().unwrap_or("").to_owned(),
second: path.display().to_string(),
});
}
out.insert(id, descriptor);
}
Ok(out)
}
fn closure(
found: &BTreeMap<String, SchemaDescriptor>,
load_list: &[String],
) -> Result<BTreeSet<String>, RmAccessError> {
let by_lower: BTreeMap<String, String> = found
.keys()
.map(|id| (id.to_lowercase(), id.clone()))
.collect();
let mut selected: BTreeSet<String> = BTreeSet::new();
let mut pending: Vec<String> = load_list.to_vec();
while let Some(wanted) = pending.pop() {
let key = by_lower
.get(&wanted.to_lowercase())
.ok_or_else(|| RmAccessError::UnknownLoadListEntry { id: wanted.clone() })?;
if !selected.insert(key.clone()) {
continue;
}
let Some(descriptor) = found.get(key) else {
continue;
};
pending.extend(descriptor.includes().iter().cloned());
}
Ok(selected)
}