Skip to main content

opcua_nodes/
references.rs

1use std::hash::Hasher;
2
3use hashbrown::{Equivalent, HashMap, HashSet};
4use opcua_types::{
5    node_id::{IdentifierRef, IntoNodeIdRef, NodeIdRef},
6    BrowseDirection, Identifier, NodeId,
7};
8
9use crate::{ImportedReference, ReferenceDirection, TypeTree};
10
11#[derive(PartialEq, Eq, Clone, Debug, Hash)]
12/// Owned OPC-UA reference.
13pub struct Reference {
14    /// Reference type ID.
15    pub reference_type: NodeId,
16    /// Target node ID.
17    pub target_node: NodeId,
18}
19
20// Note, must have same hash and eq implementation as Reference.
21#[derive(PartialEq, Eq, Clone, Debug)]
22struct ReferenceKey<R: IdentifierRef, R2: IdentifierRef> {
23    pub reference_type: NodeIdRef<R2>,
24    pub target_node: NodeIdRef<R>,
25}
26
27impl<R, R2> std::hash::Hash for ReferenceKey<R, R2>
28where
29    R: IdentifierRef,
30    R2: IdentifierRef,
31{
32    fn hash<H: Hasher>(&self, state: &mut H) {
33        self.reference_type.hash(state);
34        self.target_node.hash(state);
35    }
36}
37
38impl<R, R2> Equivalent<Reference> for ReferenceKey<R, R2>
39where
40    R: IdentifierRef,
41    R2: IdentifierRef,
42{
43    fn equivalent(&self, key: &Reference) -> bool {
44        self.reference_type.equivalent(&key.reference_type)
45            && self.target_node.equivalent(&key.target_node)
46    }
47}
48
49impl<'a> From<&'a Reference> for ReferenceKey<&'a Identifier, &'a Identifier> {
50    fn from(value: &'a Reference) -> Self {
51        Self {
52            reference_type: (&value.reference_type).into_node_id_ref(),
53            target_node: (&value.target_node).into_node_id_ref(),
54        }
55    }
56}
57
58#[derive(PartialEq, Eq, Clone, Debug, Hash)]
59/// A borrowed version of an OPC-UA reference.
60pub struct ReferenceRef<'a> {
61    /// Reference type ID.
62    pub reference_type: &'a NodeId,
63    /// Target node ID.
64    pub target_node: &'a NodeId,
65    /// Reference direction.
66    pub direction: ReferenceDirection,
67}
68
69// Note that there is a potentially significant benefit to using hashbrown directly here,
70// (which is what the std HashMap is built on!), since it lets us remove references from
71// the hash sets without cloning given node IDs.
72#[derive(Debug, Default)]
73/// Structure for storing and accessing OPC-UA references.
74pub struct References {
75    /// References by source node ID.
76    by_source: HashMap<NodeId, HashSet<Reference>>,
77    /// References by target node ID.
78    by_target: HashMap<NodeId, HashSet<Reference>>,
79}
80
81impl References {
82    /// Create a new empty reference store.
83    pub fn new() -> Self {
84        Self {
85            by_source: HashMap::new(),
86            by_target: HashMap::new(),
87        }
88    }
89
90    /// Insert a list of references.
91    pub fn insert<'a, S>(
92        &mut self,
93        source: &NodeId,
94        references: &'a [(&'a NodeId, &S, ReferenceDirection)],
95    ) where
96        S: Into<NodeId> + Clone,
97    {
98        for (target, typ, direction) in references {
99            let typ: NodeId = (*typ).clone().into();
100            match direction {
101                ReferenceDirection::Forward => self.insert_reference(source, target, typ),
102                ReferenceDirection::Inverse => self.insert_reference(target, source, typ),
103            }
104        }
105    }
106
107    /// Insert a new reference.
108    pub fn insert_reference(
109        &mut self,
110        source_node: &NodeId,
111        target_node: &NodeId,
112        reference_type: impl Into<NodeId>,
113    ) {
114        if source_node == target_node {
115            panic!("Node id from == node id to {source_node}, self reference is not allowed");
116        }
117
118        let forward_refs = match self.by_source.get_mut(source_node) {
119            Some(r) => r,
120            None => self.by_source.entry(source_node.clone()).or_default(),
121        };
122
123        let reference_type = reference_type.into();
124
125        if !forward_refs.insert(Reference {
126            reference_type: reference_type.clone(),
127            target_node: target_node.clone(),
128        }) {
129            // If the reference is already added, no reason to try adding it to the inverse.
130            return;
131        }
132
133        let inverse_refs = match self.by_target.get_mut(target_node) {
134            Some(r) => r,
135            None => self.by_target.entry(target_node.clone()).or_default(),
136        };
137
138        inverse_refs.insert(Reference {
139            reference_type,
140            target_node: source_node.clone(),
141        });
142    }
143
144    /// Insert a list of references (source, target, reference type)
145    pub fn insert_references<'a>(
146        &mut self,
147        references: impl Iterator<Item = (&'a NodeId, &'a NodeId, impl Into<NodeId>)>,
148    ) {
149        for (source, target, typ) in references {
150            self.insert_reference(source, target, typ);
151        }
152    }
153
154    /// Import a reference from a nodeset.
155    pub fn import_reference(&mut self, source_node: NodeId, rf: ImportedReference) {
156        if source_node == rf.target_id {
157            panic!("Node id from == node id to {source_node}, self reference is not allowed");
158        }
159
160        let mut source = source_node;
161        let mut target = rf.target_id;
162        if !rf.is_forward {
163            (source, target) = (target, source);
164        }
165
166        let forward_refs = match self.by_source.get_mut(&source) {
167            Some(r) => r,
168            None => self.by_source.entry(source.clone()).or_default(),
169        };
170
171        if !forward_refs.insert(Reference {
172            reference_type: rf.type_id.clone(),
173            target_node: target.clone(),
174        }) {
175            // If the reference is already added, no reason to try adding it to the inverse.
176            return;
177        }
178
179        let inverse_refs = match self.by_target.get_mut(&target) {
180            Some(r) => r,
181            None => self.by_target.entry(target).or_default(),
182        };
183
184        inverse_refs.insert(Reference {
185            reference_type: rf.type_id,
186            target_node: source,
187        });
188    }
189
190    /// Delete a reference.
191    pub fn delete_reference<'a>(
192        &mut self,
193        source_node: impl IntoNodeIdRef<'a>,
194        target_node: impl IntoNodeIdRef<'a>,
195        reference_type: impl IntoNodeIdRef<'a>,
196    ) -> bool {
197        let mut found = false;
198        let reference_type = reference_type.into_node_id_ref();
199        let source_node = source_node.into_node_id_ref();
200        let target_node = target_node.into_node_id_ref();
201        let rf = ReferenceKey {
202            reference_type,
203            target_node,
204        };
205        found |= self
206            .by_source
207            .get_mut(&source_node)
208            .map(|f| f.remove(&rf))
209            .unwrap_or_default();
210
211        let rf = ReferenceKey {
212            reference_type,
213            target_node: source_node,
214        };
215
216        found |= self
217            .by_target
218            .get_mut(&target_node)
219            .map(|f| f.remove(&rf))
220            .unwrap_or_default();
221
222        found
223    }
224
225    /// Delete references from  the given node.
226    /// Optionally deleting references _to_ the given node.
227    ///
228    /// Returns whether any references were found.
229    pub fn delete_node_references<'a>(
230        &mut self,
231        source_node: impl IntoNodeIdRef<'a>,
232        delete_target_references: bool,
233    ) -> bool {
234        let mut found = false;
235        let source_node = source_node.into_node_id_ref();
236        let source = self.by_source.remove(&source_node);
237        found |= source.is_some();
238        if delete_target_references {
239            for rf in source.into_iter().flatten() {
240                if let Some(rec) = self.by_target.get_mut(&rf.target_node) {
241                    rec.remove(&ReferenceKey {
242                        reference_type: (&rf.reference_type).into_node_id_ref(),
243                        target_node: source_node,
244                    });
245                }
246            }
247        }
248
249        let target = self.by_target.remove(&source_node);
250        found |= target.is_some();
251
252        if delete_target_references {
253            for rf in target.into_iter().flatten() {
254                if let Some(rec) = self.by_source.get_mut(&rf.target_node) {
255                    rec.remove(&ReferenceKey {
256                        reference_type: (&rf.reference_type).into_node_id_ref(),
257                        target_node: source_node,
258                    });
259                }
260            }
261        }
262
263        found
264    }
265
266    /// Return `true` if the given reference exists.
267    pub fn has_reference<'a>(
268        &self,
269        source_node: impl IntoNodeIdRef<'a>,
270        target_node: impl IntoNodeIdRef<'a>,
271        reference_type: impl IntoNodeIdRef<'a>,
272    ) -> bool {
273        let reference_type = reference_type.into_node_id_ref();
274        let target_node = target_node.into_node_id_ref();
275        self.by_source
276            .get(&source_node.into_node_id_ref())
277            .map(|n| {
278                n.contains(&ReferenceKey {
279                    reference_type,
280                    target_node,
281                })
282            })
283            .unwrap_or_default()
284    }
285
286    /// Return an iterator over references matching the given filters.
287    pub fn find_references<'a: 'b, 'b>(
288        &'a self,
289        source_node: impl IntoNodeIdRef<'b>,
290        filter: Option<(impl Into<NodeId>, bool)>,
291        type_tree: &'b dyn TypeTree,
292        direction: BrowseDirection,
293    ) -> impl Iterator<Item = ReferenceRef<'a>> + 'b {
294        ReferenceIterator::new(
295            source_node.into_node_id_ref(),
296            direction,
297            self,
298            filter.map(|f| (f.0.into(), f.1)),
299            type_tree,
300        )
301    }
302}
303
304// Handy feature to let us easily return a concrete type from `find_references`.
305struct ReferenceIterator<'a, 'b> {
306    filter: Option<(NodeId, bool)>,
307    type_tree: &'b dyn TypeTree,
308    iter_s: Option<hashbrown::hash_set::Iter<'a, Reference>>,
309    iter_t: Option<hashbrown::hash_set::Iter<'a, Reference>>,
310}
311
312impl<'a> Iterator for ReferenceIterator<'a, '_> {
313    type Item = ReferenceRef<'a>;
314
315    fn next(&mut self) -> Option<Self::Item> {
316        loop {
317            let inner = self.next_inner()?;
318
319            if let Some(filter) = &self.filter {
320                if !filter.1 && inner.reference_type != &filter.0
321                    || filter.1
322                        && !self
323                            .type_tree
324                            .is_subtype_of(inner.reference_type, &filter.0)
325                {
326                    continue;
327                }
328            }
329
330            break Some(inner);
331        }
332    }
333
334    fn size_hint(&self) -> (usize, Option<usize>) {
335        let mut lower = 0;
336        let mut upper = None;
337        if let Some(iter_s) = &self.iter_s {
338            let (lower_i, upper_i) = iter_s.size_hint();
339            lower = lower_i;
340            upper = upper_i;
341        }
342
343        if let Some(iter_t) = &self.iter_t {
344            let (lower_i, upper_i) = iter_t.size_hint();
345            lower += lower_i;
346            upper = match (upper, upper_i) {
347                (Some(l), Some(r)) => Some(l + r),
348                _ => None,
349            }
350        }
351
352        (lower, upper)
353    }
354}
355
356impl<'a, 'b> ReferenceIterator<'a, 'b> {
357    fn new<R: IdentifierRef + 'b>(
358        source_node: NodeIdRef<R>,
359        direction: BrowseDirection,
360        references: &'a References,
361        filter: Option<(NodeId, bool)>,
362        type_tree: &'b dyn TypeTree,
363    ) -> Self {
364        Self {
365            filter,
366            type_tree,
367            iter_s: matches!(direction, BrowseDirection::Both | BrowseDirection::Forward)
368                .then(|| references.by_source.get(&source_node))
369                .flatten()
370                .map(|r| r.iter()),
371            iter_t: matches!(direction, BrowseDirection::Both | BrowseDirection::Inverse)
372                .then(|| references.by_target.get(&source_node))
373                .flatten()
374                .map(|r| r.iter()),
375        }
376    }
377
378    fn next_inner(&mut self) -> Option<ReferenceRef<'a>> {
379        if let Some(iter_s) = &mut self.iter_s {
380            match iter_s.next() {
381                Some(r) => {
382                    return Some(ReferenceRef {
383                        reference_type: &r.reference_type,
384                        target_node: &r.target_node,
385                        direction: ReferenceDirection::Forward,
386                    })
387                }
388                None => self.iter_s = None,
389            }
390        }
391
392        if let Some(iter_t) = &mut self.iter_t {
393            match iter_t.next() {
394                Some(r) => {
395                    return Some(ReferenceRef {
396                        reference_type: &r.reference_type,
397                        target_node: &r.target_node,
398                        direction: ReferenceDirection::Inverse,
399                    })
400                }
401                None => self.iter_t = None,
402            }
403        }
404
405        None
406    }
407}