tramex_tools/interface/association/
matcher.rs

1//! Trace matcher for finding related traces
2
3use super::relation::AssociationStatus;
4use super::rules::{AssociationRule, SearchDirection};
5use crate::data::Trace;
6use crate::interface::layer::Layer;
7
8/// Matcher for finding related trace relationships
9/// Provides functionality to search for related traces (parent/child relationships)
10/// based on configurable association rules and search directions.
11pub struct TraceMatcher;
12
13impl TraceMatcher {
14    /// Find a relative trace using the rule's source and target layers
15    /// This is the main public API - the rule defines what we're searching for
16    ///
17    /// # Arguments
18    /// * `source_index` - Index of the source trace in the events vector
19    /// * `events` - Reference to all traces
20    /// * `rule` - The association rule to use for matching
21    /// * `source_layer` - The layer the source trace should have
22    /// * `target_layer` - The layer we're searching for
23    pub fn find_relative(
24        source_index: usize,
25        events: &[Trace],
26        rule: &dyn AssociationRule,
27        source_layer: &Layer,
28        target_layer: &Layer,
29    ) -> AssociationStatus {
30        let source = match events.get(source_index) {
31            Some(t) => t,
32            None => return AssociationStatus::NotFound,
33        };
34
35        // Check if this trace's layer matches the expected source layer
36        if &source.layer != source_layer {
37            return AssociationStatus::NotApplicable;
38        }
39
40        let direction = rule.preferred_direction(source);
41
42        // Search based on preferred direction
43        match direction {
44            SearchDirection::BackwardFirst => {
45                if let Some(idx) = Self::search_backward(source_index, events, source, rule, target_layer) {
46                    return AssociationStatus::Found(vec![idx]);
47                }
48                if let Some(idx) = Self::search_forward(source_index, events, source, rule, target_layer) {
49                    return AssociationStatus::Found(vec![idx]);
50                }
51            }
52            SearchDirection::ForwardFirst => {
53                if let Some(idx) = Self::search_forward(source_index, events, source, rule, target_layer) {
54                    return AssociationStatus::Found(vec![idx]);
55                }
56                if let Some(idx) = Self::search_backward(source_index, events, source, rule, target_layer) {
57                    return AssociationStatus::Found(vec![idx]);
58                }
59            }
60            SearchDirection::BackwardOnly => {
61                if let Some(idx) = Self::search_backward(source_index, events, source, rule, target_layer) {
62                    return AssociationStatus::Found(vec![idx]);
63                }
64            }
65            SearchDirection::ForwardOnly => {
66                if let Some(idx) = Self::search_forward(source_index, events, source, rule, target_layer) {
67                    return AssociationStatus::Found(vec![idx]);
68                }
69            }
70        }
71
72        AssociationStatus::NotFound
73    }
74
75    /// Find a relative trace with swapped source/target layers (reverse search)
76    pub fn find_relative_reverse(source_index: usize, events: &[Trace], rule: &dyn AssociationRule) -> AssociationStatus {
77        Self::find_relative(source_index, events, rule, &rule.target_layer(), &rule.source_layer())
78    }
79
80    /// Search backward (previous traces) for a matching trace
81    fn search_backward(
82        index: usize,
83        events: &[Trace],
84        source: &Trace,
85        rule: &dyn AssociationRule,
86        target_layer: &Layer,
87    ) -> Option<usize> {
88        let window_size = rule.window_size();
89        let start = index.saturating_sub(window_size);
90        for idx in (start..index).rev() {
91            let candidate = &events[idx];
92
93            if &candidate.layer != target_layer {
94                continue;
95            }
96
97            if rule.matches(source, candidate) {
98                return Some(idx);
99            }
100        }
101        None
102    }
103
104    /// Search forward (next traces) for a matching trace
105    fn search_forward(
106        index: usize,
107        events: &[Trace],
108        source: &Trace,
109        rule: &dyn AssociationRule,
110        target_layer: &Layer,
111    ) -> Option<usize> {
112        let window_size = rule.window_size();
113        let end = (index + window_size + 1).min(events.len());
114        for (idx, candidate) in events.iter().enumerate().take(end).skip(index + 1) {
115            if &candidate.layer != target_layer {
116                continue;
117            }
118
119            if rule.matches(source, candidate) {
120                return Some(idx);
121            }
122        }
123        None
124    }
125}
126
127#[cfg(test)]
128mod tests {
129    use super::*;
130
131    #[test]
132    fn test_search_direction_from_direction() {
133        use crate::interface::types::Direction;
134
135        assert_eq!(
136            SearchDirection::from_direction(&Direction::UL),
137            SearchDirection::BackwardFirst
138        );
139        assert_eq!(SearchDirection::from_direction(&Direction::DL), SearchDirection::ForwardFirst);
140    }
141}