1use super::Name;
2use super::NodeId;
3use super::ShEx2UmlConfig;
4use super::UmlCardinality;
5use super::UmlComponent;
6use super::UmlEntry;
7use super::UmlError;
8use super::UmlLink;
9use super::ValueConstraint;
10use std::collections::hash_map::*;
11use std::collections::HashMap;
12use std::collections::HashSet;
13use std::hash::Hash;
14use std::io::Write;
15
16#[derive(Debug, PartialEq, Default)]
17pub struct Uml {
18 labels_counter: usize,
19
20 labels: HashMap<String, NodeId>,
21
22 components: HashMap<NodeId, UmlComponent>,
24
25 links: Vec<UmlLink>,
27
28 extends: HashMap<NodeId, HashSet<NodeId>>,
30
31 outgoing: HashMap<NodeId, HashSet<NodeId>>,
33
34 incoming: HashMap<NodeId, HashSet<NodeId>>,
36}
37
38impl Uml {
39 pub fn new() -> Uml {
40 Default::default()
41 }
42
43 pub fn get_node_adding_label(&mut self, label: &str) -> (NodeId, bool) {
45 match self.labels.entry(label.to_string()) {
46 Entry::Occupied(c) => (*c.get(), true),
47 Entry::Vacant(v) => {
48 self.labels_counter += 1;
49 let n = NodeId::new(self.labels_counter);
50 v.insert(n);
51 (n, false)
52 }
53 }
54 }
55
56 pub fn get_node(&self, label: &str) -> Option<NodeId> {
58 self.labels.get(label).copied()
59 }
60
61 pub fn add_component(&mut self, node: NodeId, component: UmlComponent) -> Result<(), UmlError> {
62 match self.components.entry(node) {
63 Entry::Occupied(_c) => Err(UmlError::NodeIdHasComponent { node_id: node }),
64 Entry::Vacant(v) => {
65 v.insert(component);
66 Ok(())
67 }
68 }
69 }
70
71 pub fn update_component(
72 &mut self,
73 node: NodeId,
74 component: UmlComponent,
75 ) -> Result<(), UmlError> {
76 if let Some(r) = self.components.get_mut(&node) {
77 *r = component
78 } else {
79 self.components.insert(node, component);
80 }
81 Ok(())
82 }
83
84 pub fn children<'a>(
85 &'a self,
86 node: &'a NodeId,
87 ) -> impl Iterator<Item = (&'a NodeId, &'a UmlComponent)> {
88 self.components.iter().filter(|(node_id, _component)| {
89 if let Some(es) = self.extends.get(node_id) {
90 es.contains(node)
91 } else {
92 false
93 }
94 })
95 }
96
97 pub fn add_link(
98 &mut self,
99 source: NodeId,
100 target: Name,
101 link_name: Name,
102 card: UmlCardinality,
103 ) -> Result<(), UmlError> {
104 match self.labels.entry(target.name()) {
105 Entry::Occupied(entry) => {
106 let target = *entry.get();
107 self.make_link(source, target, link_name, card);
108 Ok(())
109 }
110 Entry::Vacant(v) => {
111 self.labels_counter += 1;
112 let target_node_id = NodeId::new(self.labels_counter);
113 v.insert(target_node_id);
114 self.make_link(source, target_node_id, link_name, card);
115 Ok(())
116 }
117 }
118 }
119
120 pub fn make_link(&mut self, source: NodeId, target: NodeId, name: Name, card: UmlCardinality) {
121 let link = UmlLink::new(source, target, name, card);
122 self.links.push(link);
123 insert_map(&mut self.outgoing, source, target);
124 insert_map(&mut self.incoming, target, source);
125 }
126
127 pub fn add_extends(&mut self, source: &NodeId, target: &NodeId) {
128 match self.extends.entry(*source) {
129 Entry::Occupied(mut v) => {
130 v.get_mut().insert(*target);
131 }
132 Entry::Vacant(vacant) => {
133 vacant.insert(HashSet::from([*target]));
134 }
135 }
136 }
137
138 pub fn extends(&self) -> impl Iterator<Item = (&NodeId, &NodeId)> {
139 self.extends
140 .iter()
141 .flat_map(|(n1, vs)| vs.iter().map(move |n2| (n1, n2)))
142 }
143
144 pub fn as_plantuml_all<W: Write>(
145 &self,
146 config: &ShEx2UmlConfig,
147 writer: &mut W,
148 ) -> Result<(), UmlError> {
149 writeln!(writer, "@startuml")?;
150 for (node_id, component) in self.components.iter() {
151 component2plantuml(node_id, component, config, writer)?;
152 }
153 for link in self.links.iter() {
154 link2plantuml(link, config, writer)?;
155 }
156 for (n1, n2) in self.extends() {
157 writeln!(writer, "{n1} -|> {n2}")?;
158 }
159 writeln!(writer, "@enduml")?;
160 Ok(())
161 }
162
163 pub fn as_plantuml_neighs<W: Write>(
164 &self,
165 config: &ShEx2UmlConfig,
166 writer: &mut W,
167 target_node: &NodeId,
168 ) -> Result<(), UmlError> {
169 writeln!(writer, "@startuml")?;
170 let mut serialized_components = HashSet::new();
171
172 for (node_id, component) in self.components.iter() {
174 if node_id == target_node
175 || is_in_extends(&self.extends, node_id, target_node)
176 || is_in_extends(&self.extends, target_node, node_id)
177 || is_in_map(&self.outgoing, target_node, node_id)
178 || is_in_map(&self.incoming, target_node, node_id)
179 && !serialized_components.contains(node_id)
180 {
181 serialized_components.insert(node_id);
182 component2plantuml(node_id, component, config, writer)?;
183 }
184 }
185 for link in self.links.iter() {
186 if link.source == *target_node || link.target == *target_node {
187 link2plantuml(link, config, writer)?;
188 }
189 }
190 for (n1, n2) in self.extends() {
191 if n1 == target_node || n2 == target_node {
192 writeln!(writer, "{n1} -|> {n2}")?;
193 }
194 }
195 writeln!(writer, "@enduml")?;
196 Ok(())
197 }
198}
199
200fn component2plantuml<W: Write>(
201 node_id: &NodeId,
202 component: &UmlComponent,
203 config: &ShEx2UmlConfig,
204 writer: &mut W,
205) -> Result<(), UmlError> {
206 match component {
207 UmlComponent::UmlClass(class) => {
208 let name = if config.replace_iri_by_label() {
209 if let Some(label) = class.label() {
210 label
211 } else {
212 class.name()
213 }
214 } else {
215 class.name()
216 };
217 let href = if let Some(href) = class.href() {
218 format!("[[{href} {name}]]")
219 } else {
220 "".to_string()
221 };
222 writeln!(
223 writer,
224 "class \"{}\" as {} <<(S,#FF7700)>> {} {{ ",
225 name, node_id, href
226 )?;
227 for entry in class.entries() {
228 entry2plantuml(entry, config, writer)?;
229 }
230 writeln!(writer, "}}")?;
231 }
232 }
233 Ok(())
234}
235
236fn link2plantuml<W: Write>(
237 link: &UmlLink,
238 config: &ShEx2UmlConfig,
239 writer: &mut W,
240) -> Result<(), UmlError> {
241 let source = format!("{}", link.source);
242 let card = card2plantuml(&link.card);
243 let target = format!("{}", link.target);
244 let name = name2plantuml(&link.name, config);
245 writeln!(writer, "{source} --> \"{card}\" {target} : {name}")?;
246 Ok(())
247}
248
249fn entry2plantuml<W: Write>(
250 entry: &UmlEntry,
251 config: &ShEx2UmlConfig,
252 writer: &mut W,
253) -> Result<(), UmlError> {
254 let property = name2plantuml(&entry.name, config);
255 let value_constraint = value_constraint2plantuml(&entry.value_constraint, config);
256 let card = card2plantuml(&entry.card);
257 writeln!(writer, "{} : {} {}", property, value_constraint, card)?;
258 writeln!(writer, "--")?;
259 Ok(())
260}
261
262fn name2plantuml(name: &Name, config: &ShEx2UmlConfig) -> String {
263 let str = if config.replace_iri_by_label() {
264 if let Some(label) = name.label() {
265 label
266 } else {
267 name.name()
268 }
269 } else {
270 name.name()
271 };
272 if let Some(href) = name.href() {
273 format!("[[{href} {}]]", str)
274 } else {
275 name.name()
276 }
277}
278
279fn value_constraint2plantuml(vc: &ValueConstraint, config: &ShEx2UmlConfig) -> String {
280 match vc {
281 ValueConstraint::Any => ".".to_string(),
282 ValueConstraint::Datatype(dt) => name2plantuml(dt, config),
283 ValueConstraint::Ref(r) => format!("@{}", name2plantuml(r, config)),
284 ValueConstraint::None => "".to_string(),
285 ValueConstraint::ValueSet(values) => {
286 let mut str = String::new();
287 str.push_str("[ ");
288 for value in values {
289 let name_puml = name2plantuml(value, config);
290 str.push_str(name_puml.as_str());
291 str.push_str(", ");
292 }
293 str.push_str(" ]");
294 str.to_string()
295 }
296 }
297}
298
299fn card2plantuml(card: &UmlCardinality) -> String {
300 match card {
301 UmlCardinality::OneOne => " ".to_string(),
302 UmlCardinality::Star => "*".to_string(),
303 UmlCardinality::Plus => "+".to_string(),
304 UmlCardinality::Optional => "?".to_string(),
305 UmlCardinality::Range(m, n) => format!("{m}-{n}"),
306 UmlCardinality::Fixed(m) => format!("{{{m}}}"),
307 }
308}
309
310fn is_in_extends(
311 extends: &HashMap<NodeId, HashSet<NodeId>>,
312 node: &NodeId,
313 target: &NodeId,
314) -> bool {
315 if let Some(es) = extends.get(node) {
316 es.contains(target)
317 } else {
318 false
319 }
320}
321
322fn insert_map<A, B>(map: &mut HashMap<A, HashSet<B>>, source: A, target: B)
323where
324 A: Eq + Hash,
325 B: Eq + Hash,
326{
327 match map.entry(source) {
328 Entry::Occupied(mut entry) => {
329 let set = entry.get_mut();
330 set.insert(target);
331 }
332 Entry::Vacant(v) => {
333 v.insert(HashSet::from([target]));
334 }
335 }
336}
337
338fn is_in_map<A, B>(map: &HashMap<A, HashSet<B>>, source: &A, target: &B) -> bool
339where
340 A: Eq + Hash,
341 B: Eq + Hash,
342{
343 if let Some(es) = map.get(source) {
344 es.contains(target)
345 } else {
346 false
347 }
348}