use crate::content::node::NodeState;
use crate::index::{
IndexResult, children_in_tree_order, converting_boolean, converting_long, strict_name,
strict_string,
};
const MATCH_ALL: &str = "*";
pub const DEFAULT_REAGGREGATION_LIMIT: i64 = 5;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Include {
pub node_name: String,
pub elements: Vec<String>,
pub primary_type: Option<String>,
pub relative_node: bool,
}
impl Include {
#[must_use]
pub fn maximum_depth(&self) -> usize {
self.elements.len()
}
pub fn matches(&self, name: &str, node: &NodeState<'_>, depth: usize) -> IndexResult<bool> {
let Some(element) = self.elements.get(depth) else {
return Ok(false);
};
if element != MATCH_ALL && element != name {
return Ok(false);
}
if depth + 1 == self.maximum_depth()
&& let Some(primary_type) = &self.primary_type
&& strict_name(node.property("jcr:primaryType")?.as_ref())
!= Some(primary_type.as_str())
{
return Ok(false);
}
Ok(true)
}
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct Aggregate {
pub node_type_name: String,
pub includes: Vec<Include>,
pub reaggregation_limit: i64,
}
impl Aggregate {
#[must_use]
pub fn has_node_aggregates(&self) -> bool {
!self.includes.is_empty()
}
pub fn read(node_type_name: &str, node: &NodeState<'_>) -> IndexResult<Self> {
let mut includes = Vec::new();
for (name, child) in children_in_tree_order(node)? {
if !name.starts_with("include") {
continue;
}
let path = strict_string(child.property("path")?.as_ref())
.map(str::to_owned)
.unwrap_or_default();
includes.push(Include {
node_name: name,
elements: path
.split('/')
.filter(|element| !element.is_empty())
.map(str::to_owned)
.collect(),
primary_type: strict_string(child.property("primaryType")?.as_ref())
.map(str::to_owned),
relative_node: converting_boolean(child.property("relativeNode")?.as_ref()),
});
}
Ok(Self {
node_type_name: node_type_name.to_owned(),
includes,
reaggregation_limit: converting_long(node.property("reaggregateLimit")?.as_ref())
.unwrap_or(DEFAULT_REAGGREGATION_LIMIT),
})
}
#[must_use]
pub fn matcher(&self) -> Matcher<'_> {
Matcher {
aggregate: self,
live: (0..self.includes.len()).map(|at| (at, 0usize)).collect(),
}
}
}
#[derive(Clone, Debug)]
pub struct Matcher<'aggregate> {
aggregate: &'aggregate Aggregate,
live: Vec<(usize, usize)>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Match<'aggregate> {
Stop,
Continue,
Aggregate(Vec<&'aggregate Include>),
}
impl<'aggregate> Matcher<'aggregate> {
pub fn step(&self, name: &str, node: &NodeState<'_>) -> IndexResult<(Self, Match<'aggregate>)> {
let mut live = Vec::new();
let mut ended = Vec::new();
for (at, depth) in &self.live {
let include = &self.aggregate.includes[*at];
if !include.matches(name, node, *depth)? {
continue;
}
if depth + 1 == include.maximum_depth() {
ended.push(include);
} else {
live.push((*at, depth + 1));
}
}
let outcome = if !ended.is_empty() {
Match::Aggregate(ended)
} else if live.is_empty() {
Match::Stop
} else {
Match::Continue
};
Ok((
Self {
aggregate: self.aggregate,
live,
},
outcome,
))
}
}
pub fn read_aggregates(definition: &NodeState<'_>) -> IndexResult<Vec<Aggregate>> {
let Some(aggregates) = definition.child_node("aggregates")? else {
return Ok(Vec::new());
};
let mut produced = Vec::new();
for (name, child) in children_in_tree_order(&aggregates)? {
produced.push(Aggregate::read(&name, &child)?);
}
Ok(produced)
}
#[must_use]
pub fn for_node_type(aggregates: &[Aggregate], node_type_name: &str) -> Aggregate {
aggregates
.iter()
.find(|aggregate| aggregate.node_type_name == node_type_name)
.cloned()
.unwrap_or_else(|| Aggregate {
node_type_name: node_type_name.to_owned(),
includes: Vec::new(),
reaggregation_limit: DEFAULT_REAGGREGATION_LIMIT,
})
}