use crate::compat::{HashMap, String, Vec};
use crate::core::ObjectId;
#[derive(Debug, Clone, Default)]
pub struct TreeIndex {
path_to_id: HashMap<Vec<usize>, ObjectId>,
key_to_id: HashMap<(Option<ObjectId>, String), ObjectId>,
id_to_path: HashMap<ObjectId, Vec<usize>>,
duplicated_keys: Vec<(Option<ObjectId>, String)>,
widget_of: HashMap<ObjectId, String>,
parent_of: HashMap<ObjectId, ObjectId>,
children_of: HashMap<ObjectId, Vec<ObjectId>>,
generation: u64,
root: Option<ObjectId>,
}
impl TreeIndex {
pub fn generation(&self) -> u64 {
self.generation
}
pub fn root(&self) -> Option<ObjectId> {
self.root
}
pub fn len(&self) -> usize {
self.path_to_id.len()
}
pub fn is_empty(&self) -> bool {
self.path_to_id.is_empty()
}
pub fn id_at(&self, path: &[usize]) -> Option<ObjectId> {
self.path_to_id.get(path).copied()
}
pub fn id_of_key(&self, parent: Option<ObjectId>, key: &str) -> Option<ObjectId> {
self.key_to_id.get(&(parent, String::from(key))).copied()
}
pub fn duplicate_keys(&self) -> &[(Option<ObjectId>, String)] {
&self.duplicated_keys
}
pub fn path_of(&self, id: ObjectId) -> Option<&[usize]> {
self.id_to_path.get(&id).map(Vec::as_slice)
}
pub fn widget_of(&self, id: ObjectId) -> Option<&str> {
self.widget_of.get(&id).map(String::as_str)
}
pub fn key_of(&self, id: ObjectId) -> Option<&str> {
self.key_to_id.iter().find(|(_, v)| **v == id).map(|((_, k), _)| k.as_str())
}
pub fn parent_of(&self, id: ObjectId) -> Option<ObjectId> {
self.parent_of.get(&id).copied()
}
pub fn children_of(&self, id: ObjectId) -> &[ObjectId] {
self.children_of.get(&id).map(Vec::as_slice).unwrap_or(&[])
}
pub fn keys(&self) -> Vec<(Option<ObjectId>, &str, ObjectId)> {
let mut out: Vec<(Option<ObjectId>, &str, ObjectId)> =
self.key_to_id.iter().map(|((parent, key), id)| (*parent, key.as_str(), *id)).collect();
out.sort_by(|a, b| (a.0, a.1).cmp(&(b.0, b.1)));
out
}
pub(crate) fn build(
path_to_id: HashMap<Vec<usize>, ObjectId>,
widget_of: HashMap<ObjectId, String>,
key_of: HashMap<ObjectId, String>,
parent_of: HashMap<ObjectId, ObjectId>,
children_of: HashMap<ObjectId, Vec<ObjectId>>,
root: Option<ObjectId>,
generation: u64,
) -> Self {
let mut key_to_id: HashMap<(Option<ObjectId>, String), ObjectId> = HashMap::new();
let mut duplicated_keys: Vec<(Option<ObjectId>, String)> = Vec::new();
for id in path_to_id.values() {
if let Some(key) = key_of.get(id) {
let claimed = (parent_of.get(id).copied(), key.clone());
if duplicated_keys.contains(&claimed) {
continue;
}
if key_to_id.remove(&claimed).is_some() {
duplicated_keys.push(claimed);
} else {
key_to_id.insert(claimed, *id);
}
}
}
duplicated_keys.sort();
let mut id_to_path: HashMap<ObjectId, Vec<usize>> = HashMap::new();
for (path, id) in &path_to_id {
id_to_path.insert(*id, path.clone());
}
Self {
path_to_id,
key_to_id,
duplicated_keys,
id_to_path,
widget_of,
parent_of,
children_of,
generation,
root,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sample_index() -> TreeIndex {
let mut path_to_id = HashMap::new();
path_to_id.insert(Vec::new(), 1u64);
path_to_id.insert(vec![0usize], 2u64);
path_to_id.insert(vec![0usize, 0usize], 3u64);
let mut widget_of = HashMap::new();
widget_of.insert(1u64, String::from("window"));
widget_of.insert(2u64, String::from("panel"));
widget_of.insert(3u64, String::from("label"));
let mut key_of = HashMap::new();
key_of.insert(2u64, String::from("sidebar"));
key_of.insert(3u64, String::from("title"));
let mut parent_of = HashMap::new();
parent_of.insert(2u64, 1u64);
parent_of.insert(3u64, 2u64);
let mut children_of: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
children_of.insert(1u64, vec![2u64]);
children_of.insert(2u64, vec![3u64]);
TreeIndex::build(path_to_id, widget_of, key_of, parent_of, children_of, Some(1), 7)
}
#[test]
fn path_and_id_resolve_both_ways() {
let index = sample_index();
assert_eq!(index.root(), Some(1));
assert_eq!(index.id_at(&[0]), Some(2));
assert_eq!(index.path_of(3), Some(&[0usize, 0usize][..]));
assert_eq!(index.id_at(&[9]), None);
assert_eq!(index.path_of(999), None);
}
#[test]
fn a_key_resolves_within_its_parent_only() {
let index = sample_index();
assert_eq!(index.id_of_key(Some(1), "sidebar"), Some(2));
assert_eq!(index.id_of_key(Some(2), "title"), Some(3));
assert_eq!(index.id_of_key(Some(1), "title"), None);
assert_eq!(index.id_of_key(Some(2), "sidebar"), None);
}
#[test]
fn node_type_and_parent_are_queryable() {
let index = sample_index();
assert_eq!(index.widget_of(2), Some("panel"));
assert_eq!(index.widget_of(3), Some("label"));
assert_eq!(index.parent_of(3), Some(2));
assert_eq!(index.parent_of(1), None);
assert_eq!(index.children_of(2), &[3u64]);
assert_eq!(index.children_of(3), &[] as &[ObjectId]);
}
#[test]
fn keys_are_sorted_and_complete() {
let index = sample_index();
let keys = index.keys();
assert_eq!(keys.len(), 2);
assert_eq!(keys[0], (Some(1), "sidebar", 2));
assert_eq!(keys[1], (Some(2), "title", 3));
}
#[test]
fn the_generation_is_reported() {
let index = sample_index();
assert_eq!(index.generation(), 7);
assert_eq!(index.len(), 3);
assert!(!index.is_empty());
}
#[test]
fn a_duplicated_key_is_mappable_to_neither_claimant() {
let mut path_to_id = HashMap::new();
path_to_id.insert(Vec::new(), 1u64);
path_to_id.insert(vec![0usize], 2u64);
path_to_id.insert(vec![1usize], 3u64);
let mut widget_of = HashMap::new();
widget_of.insert(1u64, String::from("window"));
widget_of.insert(2u64, String::from("label"));
widget_of.insert(3u64, String::from("label"));
let mut key_of = HashMap::new();
key_of.insert(2u64, String::from("dup"));
key_of.insert(3u64, String::from("dup"));
let mut parent_of = HashMap::new();
parent_of.insert(2u64, 1u64);
parent_of.insert(3u64, 1u64);
let mut children_of: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
children_of.insert(1u64, vec![2u64, 3u64]);
let index =
TreeIndex::build(path_to_id, widget_of, key_of, parent_of, children_of, Some(1), 1);
assert_eq!(index.id_of_key(Some(1), "dup"), None, "the key identifies neither claimant");
assert_eq!(index.duplicate_keys(), &[(Some(1), String::from("dup"))]);
assert!(index.keys().is_empty(), "{:?}", index.keys());
assert_eq!(index.key_of(2), None);
assert_eq!(index.key_of(3), None);
assert_eq!(index.id_at(&[0]), Some(2));
assert_eq!(index.id_at(&[1]), Some(3));
}
#[test]
fn a_key_claimed_three_times_is_still_unmappable() {
let mut path_to_id = HashMap::new();
path_to_id.insert(Vec::new(), 1u64);
path_to_id.insert(vec![0usize], 2u64);
path_to_id.insert(vec![1usize], 3u64);
path_to_id.insert(vec![2usize], 4u64);
let mut widget_of = HashMap::new();
for id in [1u64, 2, 3, 4] {
widget_of.insert(id, String::from("label"));
}
let mut key_of = HashMap::new();
for id in [2u64, 3, 4] {
key_of.insert(id, String::from("dup"));
}
let mut parent_of = HashMap::new();
for id in [2u64, 3, 4] {
parent_of.insert(id, 1u64);
}
let mut children_of: HashMap<ObjectId, Vec<ObjectId>> = HashMap::new();
children_of.insert(1u64, vec![2u64, 3u64, 4u64]);
let index =
TreeIndex::build(path_to_id, widget_of, key_of, parent_of, children_of, Some(1), 2);
assert_eq!(index.id_of_key(Some(1), "dup"), None, "three claimants map to none of them");
assert_eq!(index.duplicate_keys(), &[(Some(1), String::from("dup"))]);
assert_eq!(index.keys().len(), 0, "{:?}", index.keys());
}
}