use serde_json::{Map, Value};
use std::collections::{HashMap, HashSet};
use std::sync::Arc;
use crate::node_config::NodeConfigManager;
use crate::type_serialization::{
serialize_value, ExternalResolver, ResourceDisplayResolver, SerializationContext,
SerializationMode, SerializationOptions,
};
use crate::{StaticNode, StaticTile};
pub trait PseudoValueLike {
fn tile(&self) -> &Option<Arc<StaticTile>>;
fn node(&self) -> &Arc<StaticNode>;
fn is_inner(&self) -> bool;
fn is_outer(&self) -> bool;
fn datatype(&self) -> &str;
fn serialize_own_value(
&self,
serialization_options: &SerializationOptions,
serialization_context: Option<&SerializationContext>,
) -> Value;
fn has_children(&self, edges: &HashMap<String, Vec<String>>) -> bool;
fn inner(&self) -> Option<&Self>;
}
pub trait PseudoListLike {
type Value: PseudoValueLike;
fn matching_entries(
&self,
parent_tile_id: Option<String>,
nodegroup_id: Option<String>,
parent_nodegroup_id: Option<String>,
) -> Vec<&Self::Value>;
fn is_single(&self) -> bool;
fn values_count(&self) -> usize;
}
#[derive(Clone, Debug)]
pub struct PseudoValueCore {
pub node: Arc<StaticNode>,
pub child_node_ids: Vec<String>,
pub is_collector: bool,
pub inner: Option<Box<PseudoValueCore>>,
pub is_inner: bool,
pub tile: Option<Arc<StaticTile>>,
pub tile_data: Option<Value>,
pub independent: bool,
pub original_tile: Option<Arc<StaticTile>>,
}
#[derive(Clone, Debug)]
pub struct PseudoListCore {
pub node_alias: String,
pub values: Vec<PseudoValueCore>,
pub is_loaded: bool,
pub is_single: bool,
}
pub struct VisitorContext<'a, L: PseudoListLike> {
pub pseudo_cache: &'a HashMap<String, L>,
pub nodes_by_alias: &'a HashMap<String, Arc<StaticNode>>,
pub edges: &'a HashMap<String, Vec<String>>,
pub depth: usize,
pub max_depth: usize,
pub serialization_options: SerializationOptions,
pub serialization_context: SerializationContext<'a>,
pub node_config_manager: Option<&'a NodeConfigManager>,
}
pub type VisitorContextCore<'a> = VisitorContext<'a, PseudoListCore>;
impl<'a, L: PseudoListLike> VisitorContext<'a, L> {
pub fn new(
pseudo_cache: &'a HashMap<String, L>,
nodes_by_alias: &'a HashMap<String, Arc<StaticNode>>,
edges: &'a HashMap<String, Vec<String>>,
) -> Self {
VisitorContext {
pseudo_cache,
nodes_by_alias,
edges,
depth: 0,
max_depth: 50,
serialization_options: SerializationOptions::tile_data(),
serialization_context: SerializationContext::empty(),
node_config_manager: None,
}
}
pub fn display(
pseudo_cache: &'a HashMap<String, L>,
nodes_by_alias: &'a HashMap<String, Arc<StaticNode>>,
edges: &'a HashMap<String, Vec<String>>,
language: &str,
) -> Self {
VisitorContext {
pseudo_cache,
nodes_by_alias,
edges,
depth: 0,
max_depth: 50,
serialization_options: SerializationOptions::display(language),
serialization_context: SerializationContext::empty(),
node_config_manager: None,
}
}
pub fn child(&self) -> Self {
VisitorContext {
pseudo_cache: self.pseudo_cache,
nodes_by_alias: self.nodes_by_alias,
edges: self.edges,
depth: self.depth + 1,
max_depth: self.max_depth,
serialization_options: self.serialization_options.clone(),
serialization_context: self.serialization_context.with_node_config(None),
node_config_manager: self.node_config_manager,
}
}
pub fn is_display(&self) -> bool {
self.serialization_options.mode == SerializationMode::Display
}
}
#[derive(Debug, Clone)]
pub struct TileBuilder {
pub tileid: Option<String>,
pub nodegroup_id: String,
pub parenttile_id: Option<String>,
pub resourceinstance_id: String,
pub sortorder: Option<i32>,
pub data: HashMap<String, Value>,
}
pub struct TileBuilderContext<'a> {
pub pseudo_cache: &'a HashMap<String, PseudoListCore>,
pub nodes_by_alias: &'a HashMap<String, Arc<StaticNode>>,
pub edges: &'a HashMap<String, Vec<String>>,
pub resourceinstance_id: String,
pub depth: usize,
pub max_depth: usize,
pub visited_aliases: &'a std::cell::RefCell<HashSet<String>>,
}
impl PseudoValueCore {
pub fn from_node_and_tile(
node: Arc<StaticNode>,
tile: Option<Arc<StaticTile>>,
tile_data: Option<Value>,
child_node_ids: Vec<String>,
) -> Self {
let independent = tile.is_none();
let has_children = !child_node_ids.is_empty();
let is_semantic = node.datatype == "semantic";
let should_have_inner = has_children && !is_semantic;
let inner = if should_have_inner {
Some(Box::new(PseudoValueCore {
node: node.clone(),
child_node_ids: child_node_ids.clone(),
is_collector: node.is_collector,
inner: None,
is_inner: true,
tile: tile.clone(),
tile_data: None, independent,
original_tile: tile.clone(),
}))
} else {
None
};
PseudoValueCore {
node,
child_node_ids: if inner.is_some() {
vec![]
} else {
child_node_ids
},
is_collector: false,
inner,
is_inner: false,
tile: tile.clone(),
tile_data,
independent,
original_tile: tile,
}
}
pub fn is_outer(&self) -> bool {
self.inner.is_some()
}
pub fn datatype(&self) -> &str {
if self.is_inner {
"semantic"
} else {
&self.node.datatype
}
}
pub fn node_alias(&self) -> Option<&str> {
self.node.alias.as_deref()
}
pub fn has_tile(&self) -> bool {
self.tile.is_some()
}
pub fn tile_id(&self) -> Option<String> {
self.tile.as_ref().and_then(|t| t.tileid.clone())
}
pub fn has_children(&self, edges: &HashMap<String, Vec<String>>) -> bool {
edges
.get(&self.node.nodeid)
.map(|ids| !ids.is_empty())
.unwrap_or(false)
}
pub fn serialize_own_value(
&self,
serialization_options: &SerializationOptions,
serialization_context: Option<&SerializationContext>,
) -> Value {
if let Some(ref data) = self.tile_data {
let result = serialize_value(
self.datatype(),
data,
serialization_options,
serialization_context,
);
result.unwrap_or(Value::Null)
} else {
Value::Null
}
}
pub fn serialize_display(
&self,
language: &str,
node_config_manager: Option<&NodeConfigManager>,
external_resolver: Option<&dyn ExternalResolver>,
resource_resolver: Option<&dyn ResourceDisplayResolver>,
) -> Value {
let opts = SerializationOptions::display(language);
let node_config = node_config_manager.and_then(|ncm| ncm.get(&self.node.nodeid));
let ser_ctx = SerializationContext {
node_config,
external_resolver,
resource_resolver,
extension_registry: None,
};
self.serialize_own_value(&opts, Some(&ser_ctx))
}
pub fn to_json(&self, ctx: &VisitorContext<PseudoListCore>) -> Value {
to_json_generic(self, ctx)
}
pub fn collect_tiles(
&self,
ctx: &TileBuilderContext,
tiles: &mut HashMap<String, TileBuilder>,
) {
if ctx.depth > ctx.max_depth {
return;
}
if let Some(ref tile) = self.tile {
let tile_key = tile.tileid.clone().unwrap_or_else(|| {
format!("new_{}_{}", tile.nodegroup_id, tile.sortorder.unwrap_or(0))
});
if !tiles.contains_key(&tile_key) {
tiles.insert(tile_key.clone(), TileBuilder::from_tile(tile));
}
let datatype = self.datatype();
if datatype != "semantic" && !self.is_inner {
if let Some(ref data) = self.tile_data {
if let Some(tile_builder) = tiles.get_mut(&tile_key) {
tile_builder
.data
.insert(self.node.nodeid.clone(), data.clone());
}
}
}
}
let has_children = ctx
.edges
.get(&self.node.nodeid)
.map(|ids| !ids.is_empty())
.unwrap_or(false);
if self.datatype() == "semantic" || self.is_inner || has_children {
self.collect_children_tiles(ctx, tiles);
}
if let Some(ref inner) = self.inner {
let child_ctx = TileBuilderContext {
pseudo_cache: ctx.pseudo_cache,
nodes_by_alias: ctx.nodes_by_alias,
edges: ctx.edges,
resourceinstance_id: ctx.resourceinstance_id.clone(),
depth: ctx.depth + 1,
max_depth: ctx.max_depth,
visited_aliases: ctx.visited_aliases,
};
inner.collect_tiles(&child_ctx, tiles);
}
}
fn collect_children_tiles(
&self,
ctx: &TileBuilderContext,
tiles: &mut HashMap<String, TileBuilder>,
) {
let child_node_ids = match ctx.edges.get(&self.node.nodeid) {
Some(ids) => ids,
None => return,
};
for child_node_id in child_node_ids {
let child_node = ctx
.nodes_by_alias
.values()
.find(|n| &n.nodeid == child_node_id);
let child_node = match child_node {
Some(n) => n,
None => continue,
};
let child_alias = match &child_node.alias {
Some(alias) if !alias.is_empty() => alias,
_ => continue,
};
{
let visited = ctx.visited_aliases.borrow();
if visited.contains(child_alias) {
continue;
}
}
ctx.visited_aliases.borrow_mut().insert(child_alias.clone());
if let Some(pseudo_list) = ctx.pseudo_cache.get(child_alias) {
let child_ctx = TileBuilderContext {
pseudo_cache: ctx.pseudo_cache,
nodes_by_alias: ctx.nodes_by_alias,
edges: ctx.edges,
resourceinstance_id: ctx.resourceinstance_id.clone(),
depth: ctx.depth + 1,
max_depth: ctx.max_depth,
visited_aliases: ctx.visited_aliases,
};
pseudo_list.collect_tiles(&child_ctx, tiles);
}
}
}
}
impl PseudoListCore {
pub fn new(node_alias: String) -> Self {
PseudoListCore {
node_alias,
values: Vec::new(),
is_loaded: false,
is_single: false,
}
}
pub fn new_with_cardinality(node_alias: String, is_single: bool) -> Self {
PseudoListCore {
node_alias,
values: Vec::new(),
is_loaded: false,
is_single,
}
}
pub fn from_values(node_alias: String, values: Vec<PseudoValueCore>) -> Self {
PseudoListCore {
node_alias,
values,
is_loaded: true,
is_single: false,
}
}
pub fn from_values_with_cardinality(
node_alias: String,
values: Vec<PseudoValueCore>,
is_single: bool,
) -> Self {
PseudoListCore {
node_alias,
values,
is_loaded: true,
is_single,
}
}
pub fn merge(&mut self, mut other: PseudoListCore) {
self.values.append(&mut other.values);
}
pub fn matching_entries(
&self,
parent_tile_id: Option<String>,
nodegroup_id: Option<String>,
parent_nodegroup_id: Option<String>,
) -> Vec<&PseudoValueCore> {
let mut entries: Vec<&PseudoValueCore> = self
.values
.iter()
.filter(|v| {
match v.tile.as_ref() {
Some(tile) => matches_tile_filter(
tile,
parent_tile_id.as_ref(),
nodegroup_id.as_ref(),
parent_nodegroup_id.as_ref(),
),
None => parent_tile_id.is_none(),
}
})
.collect();
entries.sort_by_key(|v| {
v.tile
.as_ref()
.and_then(|t| t.sortorder)
.unwrap_or(i32::MAX)
});
entries
}
pub fn to_json(&self, ctx: &VisitorContext<PseudoListCore>) -> Value {
if self.values.is_empty() {
return Value::Null;
}
let mut sorted_values: Vec<&PseudoValueCore> = self.values.iter().collect();
sorted_values.sort_by_key(|v| {
v.tile
.as_ref()
.and_then(|t| t.sortorder)
.unwrap_or(i32::MAX)
});
if self.is_single {
if let Some(first) = sorted_values.first() {
return to_json_generic(*first, ctx);
}
return Value::Null;
}
let arr: Vec<Value> = sorted_values
.iter()
.map(|v| to_json_generic(*v, ctx))
.filter(|v| !v.is_null())
.collect();
if arr.is_empty() {
Value::Null
} else {
Value::Array(arr)
}
}
pub fn collect_tiles(
&self,
ctx: &TileBuilderContext,
tiles: &mut HashMap<String, TileBuilder>,
) {
for value in &self.values {
value.collect_tiles(ctx, tiles);
}
}
}
impl PseudoValueLike for PseudoValueCore {
fn tile(&self) -> &Option<Arc<StaticTile>> {
&self.tile
}
fn node(&self) -> &Arc<StaticNode> {
&self.node
}
fn is_inner(&self) -> bool {
self.is_inner
}
fn is_outer(&self) -> bool {
self.is_outer()
}
fn datatype(&self) -> &str {
self.datatype()
}
fn serialize_own_value(
&self,
serialization_options: &SerializationOptions,
serialization_context: Option<&SerializationContext>,
) -> Value {
self.serialize_own_value(serialization_options, serialization_context)
}
fn has_children(&self, edges: &HashMap<String, Vec<String>>) -> bool {
self.has_children(edges)
}
fn inner(&self) -> Option<&Self> {
self.inner.as_ref().map(|b| b.as_ref())
}
}
impl PseudoListLike for PseudoListCore {
type Value = PseudoValueCore;
fn matching_entries(
&self,
parent_tile_id: Option<String>,
nodegroup_id: Option<String>,
parent_nodegroup_id: Option<String>,
) -> Vec<&Self::Value> {
self.matching_entries(parent_tile_id, nodegroup_id, parent_nodegroup_id)
}
fn is_single(&self) -> bool {
self.is_single
}
fn values_count(&self) -> usize {
self.values.len()
}
}
impl TileBuilder {
pub fn from_tile(tile: &StaticTile) -> Self {
TileBuilder {
tileid: tile.tileid.clone(),
nodegroup_id: tile.nodegroup_id.clone(),
parenttile_id: tile.parenttile_id.clone(),
resourceinstance_id: tile.resourceinstance_id.clone(),
sortorder: tile.sortorder,
data: HashMap::new(),
}
}
pub fn to_static_tile(&self) -> StaticTile {
let mut tile = StaticTile::new_empty(self.nodegroup_id.clone());
tile.tileid = self.tileid.clone();
tile.parenttile_id = self.parenttile_id.clone();
tile.resourceinstance_id = self.resourceinstance_id.clone();
tile.sortorder = self.sortorder;
tile.data = self.data.clone();
tile
}
}
pub fn matches_tile_filter(
tile: &StaticTile,
parent_tile_id: Option<&String>,
nodegroup_id: Option<&String>,
parent_nodegroup_id: Option<&String>,
) -> bool {
if let Some(ng) = nodegroup_id {
if &tile.nodegroup_id == ng {
if tile.tileid.as_ref() == parent_tile_id {
return true;
}
if tile.parenttile_id.is_some() && tile.parenttile_id.as_ref() == parent_tile_id {
return true;
}
if parent_tile_id.is_none() && tile.parenttile_id.is_none() {
return true;
}
let is_different_nodegroup = match parent_nodegroup_id {
Some(parent_ng) => ng != parent_ng,
None => false,
};
if tile.parenttile_id.is_none() && parent_tile_id.is_some() && is_different_nodegroup {
return true;
}
}
}
false
}
pub fn semantic_to_json<V, L>(value: &V, ctx: &VisitorContext<L>) -> Value
where
V: PseudoValueLike,
L: PseudoListLike<Value = V>,
{
let mut obj = Map::new();
let parent_tile_id = value.tile().as_ref().and_then(|t| t.tileid.clone());
let parent_nodegroup_id = value.node().nodegroup_id.clone();
let child_node_ids = match ctx.edges.get(&value.node().nodeid) {
Some(ids) => ids,
None => {
return Value::Object(obj);
}
};
for child_node_id in child_node_ids {
let child_node = ctx
.nodes_by_alias
.values()
.find(|n| &n.nodeid == child_node_id);
let child_node = match child_node {
Some(n) => n,
None => {
continue;
}
};
let child_alias = match &child_node.alias {
Some(alias) if !alias.is_empty() => alias,
_ => {
continue;
}
};
let pseudo_list = match ctx.pseudo_cache.get(child_alias) {
Some(list) => list,
None => {
continue;
}
};
let matching_values = pseudo_list.matching_entries(
parent_tile_id.clone(),
child_node.nodegroup_id.clone(),
parent_nodegroup_id.clone(),
);
if matching_values.is_empty() {
continue;
}
let child_ctx = ctx.child();
if pseudo_list.is_single() {
if let Some(first_value) = matching_values.first() {
let json_value = to_json_generic(*first_value, &child_ctx);
if !json_value.is_null() {
obj.insert(child_alias.clone(), json_value);
}
}
} else {
let arr: Vec<Value> = matching_values
.iter()
.map(|v| to_json_generic(*v, &child_ctx))
.filter(|v| !v.is_null())
.collect();
if !arr.is_empty() {
obj.insert(child_alias.clone(), Value::Array(arr));
}
}
}
Value::Object(obj)
}
fn serialize_leaf_value<V, L>(value: &V, ctx: &VisitorContext<L>) -> Value
where
V: PseudoValueLike,
L: PseudoListLike<Value = V>,
{
let node_config = ctx
.node_config_manager
.and_then(|ncm| ncm.get(&value.node().nodeid));
let per_node_ctx = ctx.serialization_context.with_node_config(node_config);
value.serialize_own_value(&ctx.serialization_options, Some(&per_node_ctx))
}
pub fn outer_to_json<V, L>(value: &V, ctx: &VisitorContext<L>) -> Value
where
V: PseudoValueLike,
L: PseudoListLike<Value = V>,
{
let own_value = serialize_leaf_value(value, ctx);
if let Some(inner) = value.inner() {
let children_json = semantic_to_json(inner, ctx);
return ctx
.serialization_options
.merge_outer_with_children(own_value, children_json);
}
own_value
}
pub fn non_semantic_with_children_to_json<V, L>(value: &V, ctx: &VisitorContext<L>) -> Value
where
V: PseudoValueLike,
L: PseudoListLike<Value = V>,
{
let own_value = serialize_leaf_value(value, ctx);
let children_json = semantic_to_json(value, ctx);
ctx.serialization_options
.merge_outer_with_children(own_value, children_json)
}
pub fn to_json_generic<V, L>(value: &V, ctx: &VisitorContext<L>) -> Value
where
V: PseudoValueLike,
L: PseudoListLike<Value = V>,
{
if ctx.depth > ctx.max_depth {
return Value::Null;
}
if value.is_outer() {
return outer_to_json(value, ctx);
}
if value.is_inner() {
return semantic_to_json(value, ctx);
}
let datatype = value.datatype();
if datatype == "semantic" {
return semantic_to_json(value, ctx);
}
if value.has_children(ctx.edges) {
return non_semantic_with_children_to_json(value, ctx);
}
serialize_leaf_value(value, ctx)
}