use std::collections::{HashMap, HashSet};
use iri_rs::IriBuf;
use crate::{
BlankIdBuf,
Id,
LocalGenerator,
LocalTerm,
Quad,
RDF_OBJECT,
RDF_PREDICATE,
RDF_REIFIES,
RDF_SUBJECT,
RDF_TRIPLE_TERM,
RDF_TYPE,
Term,
Triple,
TripleTerm,
dataset::{DatasetMut, GraphMut, TraversableDataset, TraversableGraph},
};
#[derive(Debug, Clone, Copy)]
pub struct UnstarOptions {
pub reuse_reifier: bool,
}
impl Default for UnstarOptions {
fn default() -> Self {
Self { reuse_reifier: true }
}
}
pub fn unstar_graph<G, M, Gen>(src: &G, dest: &mut M, generator: &mut Gen, opts: UnstarOptions)
where
G: TraversableGraph<Subject = Id, Predicate = IriBuf, Object = LocalTerm>,
M: GraphMut<Subject = Id, Predicate = IriBuf, Object = LocalTerm>,
Gen: LocalGenerator,
{
let mut cache: HashMap<TripleTerm, Id> = HashMap::new();
let mut emitted: HashSet<Id> = HashSet::new();
for t in src.triples() {
if let Some(body) = reifies_body(t.1, t.2) {
cache.entry(body).or_insert_with(|| t.0.clone());
}
}
for t in src.triples() {
if reifies_body(t.1, t.2).is_some() {
let _ = unstar_object_inner(t.2.clone(), &mut |q| dest.insert(q), generator, &mut cache, &mut emitted, opts);
continue;
}
let s = t.0.clone();
let p = t.1.clone();
let o = unstar_object_inner(t.2.clone(), &mut |q| dest.insert(q), generator, &mut cache, &mut emitted, opts);
dest.insert(Triple(s, p, o));
}
}
type GraphReifyState = (HashMap<TripleTerm, Id>, HashSet<Id>);
pub fn unstar_dataset<D, M, Gen>(src: &D, dest: &mut M, generator: &mut Gen, opts: UnstarOptions)
where
D: TraversableDataset<Subject = Id, Predicate = IriBuf, Object = LocalTerm, Graph = Id>,
M: DatasetMut<Subject = Id, Predicate = IriBuf, Object = LocalTerm, Graph = Id>,
Gen: LocalGenerator,
{
let mut per_graph: HashMap<Option<Id>, GraphReifyState> = HashMap::new();
for q in src.quads() {
let g = q.3.cloned();
if let Some(body) = reifies_body(q.1, q.2) {
let (cache, _) = per_graph.entry(g).or_default();
cache.entry(body).or_insert_with(|| q.0.clone());
}
}
for q in src.quads() {
let g = q.3.cloned();
let (cache, emitted) = per_graph.entry(g.clone()).or_default();
if reifies_body(q.1, q.2).is_some() {
let g2 = g.clone();
let _ = unstar_object_inner(
q.2.clone(),
&mut |Triple(rs, rp, ro)| dest.insert(Quad(rs, rp, ro, g2.clone())),
generator,
cache,
emitted,
opts,
);
continue;
}
let s = q.0.clone();
let p = q.1.clone();
let g2 = g.clone();
let o = unstar_object_inner(
q.2.clone(),
&mut |Triple(rs, rp, ro)| dest.insert(Quad(rs, rp, ro, g2.clone())),
generator,
cache,
emitted,
opts,
);
dest.insert(Quad(s, p, o, g));
}
}
fn reifies_body(p: &IriBuf, o: &LocalTerm) -> Option<TripleTerm> {
if p.as_str() != RDF_REIFIES.as_str() {
return None;
}
match o {
LocalTerm::Triple(b) => Some((**b).clone()),
_ => None,
}
}
fn unstar_object_inner<F, Gen>(
o: LocalTerm,
emit: &mut F,
generator: &mut Gen,
cache: &mut HashMap<TripleTerm, Id>,
emitted: &mut HashSet<Id>,
opts: UnstarOptions,
) -> LocalTerm
where
F: FnMut(Triple<Id, IriBuf, LocalTerm>),
Gen: LocalGenerator,
{
match o {
LocalTerm::Triple(boxed) => {
let t = std::sync::Arc::try_unwrap(boxed).unwrap_or_else(|arc| (*arc).clone());
let inner_o = unstar_object_inner(t.2.clone(), emit, generator, cache, emitted, opts);
let inner_s = id_to_local_term(t.0.clone());
let inner_p = LocalTerm::iri(t.1.clone());
let reifier: Id = if let Some(existing) = cache.get(&t) {
existing.clone()
} else {
let fresh = Id::BlankId(next_blank(generator));
if opts.reuse_reifier {
cache.insert(t, fresh.clone());
}
fresh
};
if emitted.insert(reifier.clone()) {
emit(Triple(reifier.clone(), IriBuf::from(RDF_TYPE), LocalTerm::iri(IriBuf::from(RDF_TRIPLE_TERM))));
emit(Triple(reifier.clone(), IriBuf::from(RDF_SUBJECT), inner_s));
emit(Triple(reifier.clone(), IriBuf::from(RDF_PREDICATE), inner_p));
emit(Triple(reifier.clone(), IriBuf::from(RDF_OBJECT), inner_o));
}
id_to_local_term(reifier)
}
other => other,
}
}
#[allow(clippy::panic)]
fn next_blank<G: LocalGenerator>(generator: &mut G) -> BlankIdBuf {
match generator.next_local_term() {
LocalTerm::BlankId(b) => b,
other => panic!("LocalGenerator yielded non-blank LocalTerm during reification: {other:?}"),
}
}
fn id_to_local_term(id: Id) -> LocalTerm {
match id {
Id::Iri(i) => LocalTerm::Named(Term::Iri(i)),
Id::BlankId(b) => LocalTerm::BlankId(b),
}
}