use std::cell::RefCell;
use std::collections::HashMap;
use crate::graph::adapter::{AstAdapter, Provenance, ProvenanceList, Selection, StepPlan};
use crate::{NodeId, Value};
pub struct Rooted<A: AstAdapter> {
pub inner: A,
root: NodeId,
root_path: Option<String>,
inside: RefCell<HashMap<NodeId, bool>>,
}
impl<A: AstAdapter> Rooted<A> {
pub fn at(inner: A, path: &str) -> Result<Self, String> {
let mut cur = inner.root();
for seg in path.split('/').filter(|s| !s.is_empty()) {
let mut kids = inner.children_named(cur, seg);
if kids.is_empty() {
kids = inner
.children(cur)
.into_iter()
.filter(|&c| inner.answers_to(c, seg))
.collect();
}
match kids.len() {
1 => cur = kids[0],
0 => {
return Err(format!(
"root: no node at {path:?} ('{seg}' names nothing under {})",
name_path(&inner, cur)
));
}
n => {
return Err(format!(
"root: {path:?} names {n} nodes at '{seg}'; the root must be one node"
));
}
}
}
let root_path = inner
.provenance(cur)
.path
.filter(|p| p != "/")
.or_else(|| (cur != inner.root()).then(|| name_path(&inner, cur)));
Ok(Rooted {
inner,
root: cur,
root_path,
inside: RefCell::new(HashMap::new()),
})
}
fn inside(&self, n: NodeId) -> bool {
if n == self.root {
return true;
}
if let Some(&b) = self.inside.borrow().get(&n) {
return b;
}
let mut cur = self.inner.parent(n);
let mut found = false;
let mut steps = 0usize;
while let Some(p) = cur {
if p == self.root {
found = true;
break;
}
steps += 1;
if steps > 1_000_000 {
break;
}
cur = self.inner.parent(p);
}
self.inside.borrow_mut().insert(n, found);
found
}
fn keep(&self, nodes: Vec<NodeId>) -> Vec<NodeId> {
nodes.into_iter().filter(|&n| self.inside(n)).collect()
}
fn keep_labelled(&self, rows: Vec<(String, NodeId)>) -> Vec<(String, NodeId)> {
rows.into_iter().filter(|(_, n)| self.inside(*n)).collect()
}
fn keep_rows(
&self,
rows: Vec<(String, NodeId, Option<NodeId>)>,
) -> Vec<(String, NodeId, Option<NodeId>)> {
rows.into_iter()
.filter(|(_, n, _)| self.inside(*n))
.collect()
}
fn relative(&self, p: Provenance) -> Provenance {
let Some(prefix) = &self.root_path else {
return p;
};
let path = p.path.map(|path| match path.strip_prefix(prefix.as_str()) {
Some(rest) if rest.is_empty() => "/".to_string(),
Some(rest) if rest.starts_with('/') => rest.to_string(),
_ => path,
});
Provenance { path, ..p }
}
}
fn name_path<A: AstAdapter>(a: &A, node: NodeId) -> String {
let mut parts = Vec::new();
let mut cur = Some(node);
while let Some(n) = cur {
if n == a.root() {
break;
}
parts.push(a.name(n).unwrap_or_else(|| "*".to_string()));
cur = a.parent(n);
}
parts.reverse();
format!("/{}", parts.join("/"))
}
impl<A: AstAdapter> AstAdapter for Rooted<A> {
crate::forward_ast_adapter!(inner; except root, parent, non_tree_nodes, tree_identities,
links, backlinks, link_rows, edge_rows, backlink_rows, resolve, resolve_fragment,
document_by_ref, reverse_resolve, index_lookup, index_referrers, has_node,
provenance, provenance_list, select);
fn root(&self) -> NodeId {
self.root
}
fn parent(&self, node: NodeId) -> Option<NodeId> {
if node == self.root {
None
} else {
self.inner.parent(node)
}
}
fn non_tree_nodes(&self) -> Vec<NodeId> {
self.keep(self.inner.non_tree_nodes())
}
fn tree_identities(&self) -> Vec<(String, NodeId)> {
self.keep_labelled(self.inner.tree_identities())
}
fn links(&self, node: NodeId) -> Vec<(String, NodeId)> {
self.keep_labelled(self.inner.links(node))
}
fn backlinks(&self, node: NodeId) -> Vec<(String, NodeId)> {
self.keep_labelled(self.inner.backlinks(node))
}
fn link_rows(&self, node: NodeId) -> Vec<(String, NodeId, Option<NodeId>)> {
self.keep_rows(self.inner.link_rows(node))
}
fn edge_rows(&self, node: NodeId) -> Vec<(String, NodeId, Option<NodeId>)> {
self.keep_rows(self.inner.edge_rows(node))
}
fn backlink_rows(&self, node: NodeId) -> Vec<(String, NodeId, Option<NodeId>)> {
self.keep_rows(self.inner.backlink_rows(node))
}
fn resolve(&self, node: NodeId, property: &str, hint: Option<&str>) -> Option<NodeId> {
self.inner
.resolve(node, property, hint)
.filter(|&t| self.inside(t))
}
fn resolve_fragment(&self, node: NodeId, fragment: &str) -> Option<NodeId> {
self.inner
.resolve_fragment(node, fragment)
.filter(|&t| self.inside(t))
}
fn document_by_ref(&self, id: &str) -> Option<NodeId> {
self.inner.document_by_ref(id).filter(|&t| self.inside(t))
}
fn reverse_resolve(
&self,
node: NodeId,
property: Option<&str>,
hint: Option<&str>,
) -> Option<Vec<NodeId>> {
self.inner
.reverse_resolve(node, property, hint)
.map(|v| self.keep(v))
}
fn index_lookup(&self, scope: &str, property: &str, value: &Value) -> Option<Vec<NodeId>> {
self.inner
.index_lookup(scope, property, value)
.map(|v| self.keep(v))
}
fn index_referrers(
&self,
scope: &str,
property: &str,
label: Option<&str>,
target: NodeId,
) -> Option<Vec<NodeId>> {
self.inner
.index_referrers(scope, property, label, target)
.map(|v| self.keep(v))
}
fn has_node(&self, node: NodeId) -> bool {
self.inner.has_node(node) && self.inside(node)
}
fn provenance(&self, node: NodeId) -> Provenance {
self.relative(self.inner.provenance(node))
}
fn provenance_list(&self, node: NodeId) -> ProvenanceList {
let list = self.inner.provenance_list(node);
ProvenanceList {
entries: list.entries.into_iter().map(|p| self.relative(p)).collect(),
..list
}
}
fn select(&self, from: NodeId, plan: &StepPlan<'_>) -> Option<Selection> {
self.inner.select(from, plan).map(|s| Selection {
nodes: s
.nodes
.into_iter()
.filter(|(n, _)| self.inside(*n))
.collect(),
..s
})
}
}