Skip to main content

a2lfile/cleanup/
groups.rs

1use std::collections::HashSet;
2
3use crate::specification::{Group, Module};
4
5pub(crate) fn cleanup(module: &mut Module) {
6    // in all groups, remove references to non-existent CHARACTERISTICs, MEASUREMENTs, etc.
7    remove_invalid_object_references(module);
8
9    // remove all empty groups
10    delete_empty_groups(module);
11}
12
13fn remove_invalid_object_references(module: &mut Module) {
14    // a set of the names that a group could potentially refer to
15    let refnames = build_refname_set(module);
16
17    for grp in &mut module.group {
18        if let Some(ref_characteristic) = &mut grp.ref_characteristic {
19            // retain only references to existing characteristics
20            ref_characteristic
21                .identifier_list
22                .retain(|item| refnames.contains(item));
23            if ref_characteristic.identifier_list.is_empty() {
24                grp.ref_characteristic = None;
25            }
26        }
27        if let Some(ref_measurement) = &mut grp.ref_measurement {
28            // retain only references to existing measurements
29            ref_measurement
30                .identifier_list
31                .retain(|item| refnames.contains(item));
32            if ref_measurement.identifier_list.is_empty() {
33                grp.ref_measurement = None;
34            }
35        }
36    }
37}
38
39fn build_refname_set(module: &Module) -> HashSet<String> {
40    let mut refnames = HashSet::new();
41    for name in module.characteristic.keys() {
42        refnames.insert(name.clone());
43    }
44    for name in module.measurement.keys() {
45        refnames.insert(name.clone());
46    }
47    for name in module.blob.keys() {
48        refnames.insert(name.clone());
49    }
50    for name in module.instance.keys() {
51        refnames.insert(name.clone());
52    }
53
54    refnames
55}
56
57fn delete_empty_groups(module: &mut Module) {
58    let used_groups = get_used_groups(module);
59    let mut user_of = vec![Vec::<usize>::new(); module.group.len()];
60    let mut delete_queue: Vec<usize> = vec![];
61    for (idx, grp) in module.group.iter().enumerate() {
62        // build up a reverse reference list, i.e. for each group, which other groups list it as a sub-group
63        if let Some(sub_group) = &grp.sub_group {
64            for name in &sub_group.identifier_list {
65                if let Some(subidx) = module.group.index(name) {
66                    user_of[subidx].push(idx);
67                }
68            }
69        }
70
71        // detect which groups are empty
72        if !used_groups.contains(&grp.name) && is_group_empty(grp) {
73            delete_queue.push(idx);
74        }
75    }
76    let mut to_delete = vec![false; module.group.len()];
77    // for each group that is queued for deletion, remove all references to it
78    while let Some(del_idx) = delete_queue.pop() {
79        let name = module.group[del_idx].name.clone();
80        to_delete[del_idx] = true;
81
82        // for all groups that have a sub-group reference to this to-be-deleted group
83        for refidx in &user_of[del_idx] {
84            if let Some(sg) = &mut module.group[*refidx].sub_group {
85                // remove the reference to the deleted group from the sub-group list of the referencing group
86                sg.identifier_list.retain(|item| *item != name);
87                if sg.identifier_list.is_empty() {
88                    module.group[*refidx].sub_group = None;
89                }
90            }
91            // if the group referencing the current group became empty after the
92            // removal of the reference, then it is also queued for deletion
93            if !used_groups.contains(&module.group[*refidx].name)
94                && is_group_empty(&module.group[*refidx])
95            {
96                delete_queue.push(*refidx);
97            }
98        }
99    }
100
101    let mut del_iter = to_delete.iter();
102    module.group.retain(|_| !del_iter.next().unwrap());
103}
104
105fn get_used_groups(module: &Module) -> HashSet<String> {
106    let mut used_groups = HashSet::<String>::new();
107    for user_rights in &module.user_rights {
108        for ref_group in &user_rights.ref_group {
109            for groupname in &ref_group.identifier_list {
110                used_groups.insert(groupname.to_owned());
111            }
112        }
113    }
114
115    used_groups
116}
117
118fn is_group_empty(group: &Group) -> bool {
119    let sub_group_empty = if let Some(sg) = &group.sub_group {
120        sg.identifier_list.is_empty()
121    } else {
122        true
123    };
124
125    let ref_measurement_empty = if let Some(rm) = &group.ref_measurement {
126        rm.identifier_list.is_empty()
127    } else {
128        true
129    };
130
131    let ref_characteristic_empty = if let Some(rc) = &group.ref_characteristic {
132        rc.identifier_list.is_empty()
133    } else {
134        true
135    };
136
137    // no need to look at ANNOTATION or FUNCTION_LIST - if there are no characteristics,
138    // measurements, or sub groups, then the group is no longer useful
139
140    sub_group_empty && ref_characteristic_empty && ref_measurement_empty
141}