Skip to main content

autosar_data/
autosarmodel.rs

1use std::{collections::HashMap, hash::Hash};
2
3use crate::*;
4
5#[derive(Debug)]
6// actions to be taken when merging two elements
7enum MergeAction {
8    MergeEqual,
9    MergeUnequal(Element),
10    AOnly,
11    BOnly(usize),
12}
13
14// a single mutation performed by the merge, which can be undone again
15//
16// merge_sub_elements can recover from a failed merge of two elements by importing the element
17// of the incoming file as an additional sub element instead. That is only correct if the failed
18// merge is undone first: the merge works in-place, so by the time it fails it may already have
19// moved sub elements of the incoming element into the existing element. Without the rollback
20// these sub elements would end up in the content of both elements at once.
21enum MergeUndo {
22    // an element of the incoming file was moved into the content of new_parent
23    Imported {
24        new_parent: Element,
25        element: Element,
26        old_parent: ElementOrModel,
27        old_file_membership: HashSet<WeakArxmlFile>,
28    },
29    // the file membership of an existing element was replaced
30    FileMembership {
31        element: Element,
32        old_file_membership: HashSet<WeakArxmlFile>,
33    },
34    // a single file was added to the non-empty file membership of an existing element
35    FileMembershipExtended {
36        element: Element,
37        file: WeakArxmlFile,
38    },
39}
40
41/// Replace `old_prefix` with `new_prefix` in `path`.
42///
43/// Returns `None` if `path` is neither `old_prefix` itself nor nested inside it. Unlike a plain
44/// `str::starts_with`, the prefix comparison respects the '/' path component boundaries, so
45/// "/package10/Elem" is not considered to be inside "/package1".
46pub(crate) fn replace_path_prefix(path: &str, old_prefix: &str, new_prefix: &str) -> Option<String> {
47    let suffix = path.strip_prefix(old_prefix)?;
48    if suffix.is_empty() || suffix.starts_with('/') {
49        Some(format!("{new_prefix}{suffix}"))
50    } else {
51        None
52    }
53}
54
55/// `PathRemap` describes how Autosar paths change as the result of a rename or move operation
56///
57/// Each entry maps an old path prefix to a new one. Renaming an element and moving an identifiable
58/// element both produce a single entry. Several entries are needed when a non-identifiable element
59/// which contains identifiable sub-elements is moved: in that case there is no single old path
60/// prefix which covers exactly the moved elements.
61pub(crate) struct PathRemap(Vec<(String, String)>);
62
63impl PathRemap {
64    /// create a `PathRemap` for a single subtree whose path changes from `old` to `new`
65    pub(crate) fn single(old: String, new: String) -> Self {
66        PathRemap(vec![(old, new)])
67    }
68
69    /// create a `PathRemap` from a list of (old prefix, new prefix) pairs
70    pub(crate) fn new(remap: Vec<(String, String)>) -> Self {
71        PathRemap(remap)
72    }
73
74    /// map an old Autosar path to its new value
75    ///
76    /// Returns `None` if the path is not affected by this remapping.
77    pub(crate) fn map(&self, path: &str) -> Option<String> {
78        self.0.iter().find_map(|(old, new)| replace_path_prefix(path, old, new))
79    }
80
81    /// returns true if this remapping does not change any path
82    pub(crate) fn is_noop(&self) -> bool {
83        self.0.iter().all(|(old, new)| old == new)
84    }
85}
86
87impl AutosarModel {
88    /// Create an `AutosarData` model
89    ///
90    /// Initially it contains no arxml files and only has a default `<AUTOSAR>` element
91    ///
92    /// # Example
93    ///
94    /// ```
95    /// # use autosar_data::*;
96    /// let model = AutosarModel::new();
97    /// ```
98    ///
99    #[must_use]
100    pub fn new() -> AutosarModel {
101        let version = AutosarVersion::LATEST;
102        let xsi_schemalocation =
103            CharacterData::String(format!("http://autosar.org/schema/r4.0 {}", version.filename()));
104        let xmlns = CharacterData::String("http://autosar.org/schema/r4.0".to_string());
105        let xmlns_xsi = CharacterData::String("http://www.w3.org/2001/XMLSchema-instance".to_string());
106        let root_attributes = smallvec::smallvec![
107            Attribute {
108                attrname: AttributeName::xsiSchemalocation,
109                content: xsi_schemalocation
110            },
111            Attribute {
112                attrname: AttributeName::xmlns,
113                content: xmlns
114            },
115            Attribute {
116                attrname: AttributeName::xmlnsXsi,
117                content: xmlns_xsi
118            },
119        ];
120        let root_elem = ElementRaw {
121            parent: ElementOrModel::None,
122            elemname: ElementName::Autosar,
123            elemtype: ElementType::ROOT,
124            content: SmallVec::new(),
125            attributes: root_attributes,
126            file_membership: None,
127            comment: None,
128        }
129        .wrap();
130        let model = AutosarModelRaw {
131            files: Arc::new(parking_lot::Mutex::new(Vec::new())),
132            identifiables: FxIndexMap::default(),
133            reference_origins: FxHashMap::default(),
134            relative_references: FxHashMap::default(),
135            root_element: root_elem.clone(),
136        }
137        .wrap();
138        root_elem.set_parent(ElementOrModel::Model(model.downgrade()));
139        model
140    }
141
142    /// Create a new [`ArxmlFile`] inside this `AutosarData` structure
143    ///
144    /// You must provide a filename for the [`ArxmlFile`], even if you do not plan to write the data to disk.
145    /// You must also specify an [`AutosarVersion`]. All methods manipulation the data insdie the file will ensure conformity with the version specified here.
146    /// The newly created `ArxmlFile` will be created with a root AUTOSAR element.
147    ///
148    /// # Parameters
149    ///
150    ///  - `filename`: The name of the file to create in the model. It must be unique within the model.
151    ///    It is not created on disk until `write()` is called.
152    ///  - `version`: The [`AutosarVersion`] that will be used by the data created inside this file
153    ///
154    /// # Example
155    ///
156    /// ```
157    /// # use autosar_data::*;
158    /// # fn main() -> Result<(), AutosarDataError> {
159    /// let model = AutosarModel::new();
160    /// let file = model.create_file("filename.arxml", AutosarVersion::Autosar_00050)?;
161    /// # Ok(())
162    /// # }
163    /// ```
164    ///
165    /// # Errors
166    ///
167    ///  - [`AutosarDataError::DuplicateFilenameError`]: The model already contains a file with this filename
168    ///
169    pub fn create_file<P: AsRef<Path>>(
170        &self,
171        filename: P,
172        version: AutosarVersion,
173    ) -> Result<ArxmlFile, AutosarDataError> {
174        // the file list lock is held for the whole operation, so that concurrent calls of
175        // create_file / load_buffer / remove_file cannot interleave
176        let file_list = self.file_list();
177        let mut locked_file_list = file_list.lock();
178
179        if locked_file_list.iter().any(|af| af.filename() == filename.as_ref()) {
180            return Err(AutosarDataError::DuplicateFilenameError {
181                verb: "create",
182                filename: filename.as_ref().to_path_buf(),
183            });
184        }
185
186        let new_file = ArxmlFile::new(filename, version, self);
187
188        locked_file_list.push(new_file.clone());
189
190        // every file contains the root element (but not its children)
191        let _ = self.root_element().add_to_file_restricted(&new_file);
192
193        Ok(new_file)
194    }
195
196    /// Load a named buffer containing arxml data
197    ///
198    /// If you have e.g. received arxml data over a network, or decompressed it from an archive, etc, then you may load it with this method.
199    ///
200    /// # Parameters:
201    ///
202    ///  - `buffer`: The data inside the buffer must be valid utf-8. Optionally it may begin with a UTF-8-BOM, which will be silently ignored.
203    ///  - `filename`: the original filename of the data, or a newly generated name that is unique within the `AutosarData` instance.
204    ///  - `strict`: toggle strict parsing. Some parsing errors are recoverable and can be issued as warnings.
205    ///
206    /// This method may be called concurrently on multiple threads to load different buffers
207    ///
208    /// # Example
209    ///
210    /// ```no_run
211    /// # use autosar_data::*;
212    /// # fn main() -> Result<(), AutosarDataError> {
213    /// let model = AutosarModel::new();
214    /// # let buffer = b"";
215    /// model.load_buffer(buffer, "filename.arxml", true)?;
216    /// # Ok(())
217    /// # }
218    /// ```
219    ///
220    /// # Errors
221    ///
222    ///  - [`AutosarDataError::DuplicateFilenameError`]: The model already contains a file with this filename
223    ///  - [`AutosarDataError::OverlappingDataError`]: The new data contains Autosar paths that are already defined by the existing data
224    ///  - [`AutosarDataError::ParserError`]: The parser detected an error; the source field gives further details
225    ///
226    pub fn load_buffer<P: AsRef<Path>>(
227        &self,
228        buffer: &[u8],
229        filename: P,
230        strict: bool,
231    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarDataError> {
232        self.load_buffer_internal(buffer, filename.as_ref().to_path_buf(), strict)
233    }
234
235    fn load_buffer_internal(
236        &self,
237        buffer: &[u8],
238        filename: PathBuf,
239        strict: bool,
240    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarDataError> {
241        // quick check for a duplicate filename, so that duplicate data is rejected before the
242        // expensive parsing step. This check is not authoritative: it is repeated below while
243        // the file list lock is held
244        if self.file_list().lock().iter().any(|file| file.filename() == filename) {
245            return Err(AutosarDataError::DuplicateFilenameError { verb: "load", filename });
246        }
247
248        // no lock is held while parsing, so any number of files can be parsed in parallel
249        let mut parser = ArxmlParser::new(filename.clone(), buffer, strict);
250        parser.model = self.downgrade();
251        let root_element = parser.parse_arxml()?;
252        let version = parser.get_fileversion();
253        let arxml_file = ArxmlFileRaw {
254            version,
255            model: self.downgrade(),
256            filename: filename.clone(),
257            xml_standalone: parser.get_standalone(),
258        }
259        .wrap();
260
261        // the file list lock is held from here to the end of the load operation. It serializes
262        // the integration of the parsed data into the model
263        let file_list = self.file_list();
264        let mut locked_file_list = file_list.lock();
265
266        if locked_file_list.iter().any(|file| file.filename() == filename) {
267            return Err(AutosarDataError::DuplicateFilenameError { verb: "load", filename });
268        }
269
270        if locked_file_list.is_empty() {
271            root_element.set_parent(ElementOrModel::Model(self.downgrade()));
272            root_element.0.write().insert_file_membership(arxml_file.downgrade());
273            self.0.write().root_element = root_element;
274        } else {
275            let result = self.merge_file_data(&root_element, arxml_file.downgrade(), &locked_file_list);
276            if let Err(error) = result {
277                let _ = self.root_element().remove_from_file(&arxml_file);
278                return Err(error);
279            }
280        }
281
282        let mut data = self.0.write();
283        // import identifiables from the parser, check for conflicts with existing data
284        data.identifiables.reserve(parser.identifiables.len());
285        let mut overlap_path = None;
286        for (key, value) in parser.identifiables {
287            // the same identifiables can be present in multiple files
288            // in this case we only keep the first one
289            if let Some(existing_element) = data.identifiables.get(&key).and_then(WeakElement::upgrade) {
290                // present in both
291                if let Some(new_element) = value.upgrade()
292                    && existing_element.element_name() != new_element.element_name()
293                {
294                    // referenced element is different on both sides
295                    overlap_path = Some(new_element.xml_path());
296                    break;
297                }
298            } else {
299                data.identifiables.insert(key, value);
300            }
301        }
302        if let Some(path) = overlap_path {
303            // undo the partial merge of the new file
304            drop(data);
305            let _ = self.root_element().remove_from_file(&arxml_file);
306            return Err(AutosarDataError::OverlappingDataError { filename, path });
307        }
308
309        // import references from the parser. The parser only knows the character data of each
310        // reference, so relative references are recorded as unresolved here; they are resolved once the
311        // whole file has been merged and its REFERENCE-BASE declarations are known.
312        data.reference_origins.reserve(parser.references.len());
313        for (refpath, referring_element, base) in parser.references {
314            if base.is_some() {
315                data.relative_references.insert(referring_element, None);
316            } else {
317                data.reference_origins
318                    .entry(refpath)
319                    .or_default()
320                    .push(referring_element);
321            }
322        }
323
324        locked_file_list.push(arxml_file.clone());
325        drop(data);
326
327        // The relative references of the new file could not be resolved while it was being loaded. The
328        // new file may also declare REFERENCE-BASEs which change what the relative references of the
329        // files loaded before it resolve to, so every relative reference is resolved here, not just the
330        // ones which were just added.
331        self.resolve_relative_references();
332
333        Ok((arxml_file, parser.warnings))
334    }
335
336    // Merge the elements from an incoming arxml file into the overall model
337    //
338    // The Autosar standard specifies that the data can be split across multiple arxml files
339    // It states that each ARXML file can represent an "AUTOSAR Partial Model".
340    // The possible partitioning is marked in the meta model, where some elements have the attribute "splitable".
341    // These are the points where the overall elements can be split into different arxml files, or, while loading, merged.
342    // Unfortunately, the standard says nothing about how this should be done, so the algorithm here is just a guess.
343    // In the wild, only merging at the AR-PACKAGES and at the ELEMENTS level exists. Everything else seems like a bad idea anyway.
344    fn merge_file_data(
345        &self,
346        new_root: &Element,
347        new_file: WeakArxmlFile,
348        file_list: &[ArxmlFile],
349    ) -> Result<(), AutosarDataError> {
350        let root = self.root_element();
351        let files: HashSet<WeakArxmlFile> = file_list.iter().map(ArxmlFile::downgrade).collect();
352
353        // log of all mutations performed by the merge, so that merge_sub_elements can undo the
354        // partial merge of a pair of elements which turns out to be unmergeable
355        let mut undo = Vec::new();
356
357        Self::merge_element(&root, &files, new_root, &new_file, &mut undo).map_err(|e| {
358            // transform ElementInsertionConflict into InvalidFileMerge
359            if let AutosarDataError::ElementInsertionConflict { parent_path, .. } = &e {
360                AutosarDataError::InvalidFileMerge {
361                    path: parent_path.clone(),
362                }
363            } else {
364                e
365            }
366        })?;
367
368        self.root_element().0.write().insert_file_membership(new_file);
369
370        Ok(())
371    }
372
373    fn merge_element(
374        parent_a: &Element,
375        files: &HashSet<WeakArxmlFile>,
376        parent_b: &Element,
377        new_file: &WeakArxmlFile,
378        undo: &mut Vec<MergeUndo>,
379    ) -> Result<(), AutosarDataError> {
380        let mut iter_a = parent_a.sub_elements().enumerate();
381        let mut iter_b = parent_b.sub_elements();
382        let mut item_a = iter_a.next();
383        let mut item_b = iter_b.next();
384        let mut elements_a_only = Vec::<Element>::new();
385        let mut elements_b_only = Vec::<(Element, usize)>::new();
386        let mut elements_merge = Vec::<(Element, Element)>::new();
387        // lazily built lookup maps over the children of parent_b, and the set of b-elements
388        // already consumed by a MergeUnequal match. Linear searches for each element would
389        // make the merge quadratic in the number of sub elements
390        let mut b_item_name_map: Option<HashMap<(ElementName, Option<String>), Element>> = None;
391        let mut b_defref_map: Option<HashMap<(ElementName, Option<String>), Element>> = None;
392        let mut merged_b_elements: HashSet<WeakElement> = HashSet::new();
393        let min_ver_a = files
394            .iter()
395            .filter_map(|weak| weak.upgrade().map(|f| f.version()))
396            .min()
397            .unwrap_or(AutosarVersion::LATEST);
398        let min_ver_b = new_file.upgrade().map_or(AutosarVersion::LATEST, |f| f.version());
399        let version = std::cmp::min(min_ver_a, min_ver_b);
400        let splitable = parent_a.element_type().splittable_in(version);
401
402        while let (Some((pos_a, elem_a)), Some(elem_b)) = (&item_a, &item_b) {
403            let merge_action = if elem_a.element_name() == elem_b.element_name() {
404                if elem_a.is_identifiable() {
405                    Self::calc_identifiables_merge(parent_a, parent_b, elem_a, elem_b, splitable, &mut b_item_name_map)?
406                } else {
407                    Self::calc_element_merge(parent_b, elem_a, elem_b, &mut b_defref_map)
408                }
409            } else {
410                // a and b are different kinds of elements. This is only allowed if parent is splittable
411                let parent_type = parent_a.element_type();
412                // The following check does not work, real examples still fail:
413                // if !parent_type.splittable_in(self.version()) && parent_a.element_name() != ElementName::ArPackage {
414                //     return Err(AutosarDataError::InvalidFileMerge { path: parent_a.xml_path() });
415                // }
416
417                let (_, indices_a) = parent_type.find_sub_element(elem_a.element_name(), u32::MAX).unwrap();
418                let (_, indices_b) = parent_type.find_sub_element(elem_b.element_name(), u32::MAX).unwrap();
419                if indices_a < indices_b {
420                    // elem_a comes before elem_b, advance only a
421                    // a: <parent> | <a = child 1> <child 2>
422                    // b: <parent> |               <b = child 2>
423                    MergeAction::AOnly
424                } else {
425                    // elem_b comes before elem_a, advance only b
426                    // a: <parent> |               <a = child 2>
427                    // b: <parent> | <b = child 1> <child 2>
428                    MergeAction::BOnly(*pos_a)
429                }
430            };
431
432            match merge_action {
433                MergeAction::MergeEqual => {
434                    elements_merge.push((elem_a.clone(), elem_b.clone()));
435                    item_a = iter_a.next();
436                    item_b = iter_b.next();
437                }
438                MergeAction::MergeUnequal(other_b) => {
439                    merged_b_elements.insert(other_b.downgrade());
440                    elements_merge.push((elem_a.clone(), other_b));
441                    item_a = iter_a.next();
442                }
443                MergeAction::AOnly => {
444                    elements_a_only.push(elem_a.clone());
445                    item_a = iter_a.next();
446                }
447                MergeAction::BOnly(position) => {
448                    if !merged_b_elements.contains(&elem_b.downgrade()) {
449                        elements_b_only.push((elem_b.clone(), position));
450                    }
451                    item_b = iter_b.next();
452                }
453            }
454        }
455        // at least one of the two iterators has reached the end
456        // make sure the other one also reaches the end
457        if let Some((_, elem_a)) = item_a {
458            elements_a_only.push(elem_a);
459            for (_, elem_a) in iter_a {
460                elements_a_only.push(elem_a);
461            }
462        }
463        if let Some(elem_b) = item_b {
464            let elem_count = parent_a.0.read().content.len();
465            if !merged_b_elements.contains(&elem_b.downgrade()) {
466                elements_b_only.push((elem_b, elem_count));
467            }
468            for elem_b in iter_b {
469                if !merged_b_elements.contains(&elem_b.downgrade()) {
470                    elements_b_only.push((elem_b, elem_count));
471                }
472            }
473        }
474
475        // elements in elements_a_only are already present in the model, so they only need to be restricted
476        for element in elements_a_only {
477            // files contains the permisions of the parent
478            let restricted = {
479                let mut elem_locked = element.0.write();
480                let restricted = elem_locked.file_membership.is_none();
481                if restricted {
482                    elem_locked.set_file_membership(files.clone());
483                }
484                restricted
485            };
486            if restricted {
487                undo.push(MergeUndo::FileMembership {
488                    element,
489                    old_file_membership: HashSet::new(),
490                });
491            }
492        }
493
494        // elements in elements_b_only are not present in the model yet, so they need to be added
495        // this step can fail, in which case the merge of this element fails
496        Self::import_new_items(parent_a, elements_b_only, new_file, min_ver_b, undo)?;
497
498        // recurse for sub elements that are present on both sides: these need to be checked and merged
499        Self::merge_sub_elements(parent_a, elements_merge, files, new_file, version, undo)?;
500
501        Ok(())
502    }
503
504    // calculate how to merge two identifiable elements
505    // precondition: both elements have the same element_name
506    fn calc_identifiables_merge(
507        parent_a: &Element,
508        parent_b: &Element,
509        elem_a: &Element,
510        elem_b: &Element,
511        splitable: bool,
512        b_item_name_map: &mut Option<HashMap<(ElementName, Option<String>), Element>>,
513    ) -> Result<MergeAction, AutosarDataError> {
514        Ok(if elem_a.item_name() == elem_b.item_name() {
515            // equal
516            // advance both iterators
517            MergeAction::MergeEqual
518        } else {
519            // assume that the ordering on both sides is different
520            // find a match for a among the siblings of b, using a lookup map that is built once
521            // per parent; a linear search for each element would make the merge quadratic
522            let map = b_item_name_map.get_or_insert_with(|| {
523                let mut map = HashMap::new();
524                for e in parent_b.sub_elements() {
525                    // or_insert: keep the first element for each key, like the linear search did
526                    map.entry((e.element_name(), e.item_name())).or_insert(e);
527                }
528                map
529            });
530            if let Some(sibling) = map.get(&(elem_a.element_name(), elem_a.item_name())) {
531                // matching item found
532                MergeAction::MergeUnequal(sibling.clone())
533            } else {
534                // element is unique in a
535                if splitable {
536                    MergeAction::AOnly
537                } else {
538                    return Err(AutosarDataError::InvalidFileMerge {
539                        path: parent_a.xml_path(),
540                    });
541                }
542            }
543        })
544    }
545
546    // calculate how to merge two elements which are not identifiable
547    // precondition: both elements have the same element_name
548    fn calc_element_merge(
549        parent_b: &Element,
550        elem_a: &Element,
551        elem_b: &Element,
552        b_defref_map: &mut Option<HashMap<(ElementName, Option<String>), Element>>,
553    ) -> MergeAction {
554        // special case for BSW parameters - many elements used here don't have a SHORT-NAME, but they do have a DEFINITION-REF
555        let defref_a = elem_a
556            .get_sub_element(ElementName::DefinitionRef)
557            .and_then(|dr| dr.character_data())
558            .and_then(|cdata| cdata.string_value());
559        let defref_b = elem_b
560            .get_sub_element(ElementName::DefinitionRef)
561            .and_then(|dr| dr.character_data())
562            .and_then(|cdata| cdata.string_value());
563        // defref_a and _b are simply None for all other elements which don't have a definition-ref
564
565        if defref_a == defref_b {
566            // either: defrefs exist and are identical, OR they are both None.
567            if elem_a.character_data() != elem_b.character_data() {
568                // they have different character data, so they are not identical
569                // take only a, defer b
570                MergeAction::AOnly
571            } else {
572                // if they are None, then there is nothing else that can be compared, so we just assume the elements are identical.
573                // Merge them and advance both iterators.
574                MergeAction::MergeEqual
575            }
576        } else {
577            // check if a sibling of elem_b has the same definiton-ref as elem_a
578            // this handles the case where the elements on both sides are ordered differently.
579            // The lookup map is built once per parent; a linear search for each element would
580            // make the merge quadratic
581            let map = b_defref_map.get_or_insert_with(|| {
582                let mut map = HashMap::new();
583                for e in parent_b.sub_elements() {
584                    let defref = e
585                        .get_sub_element(ElementName::DefinitionRef)
586                        .and_then(|dr| dr.character_data())
587                        .and_then(|cdata| cdata.string_value());
588                    // or_insert: keep the first element for each key, like the linear search did
589                    map.entry((e.element_name(), defref)).or_insert(e);
590                }
591                map
592            });
593            if let Some(sibling) = map.get(&(elem_a.element_name(), defref_a)) {
594                // a match for item_a exists
595                MergeAction::MergeUnequal(sibling.clone())
596            } else {
597                // element is unique in A
598                // This case only happens for BSW definition elements, and it appears that these always have a splittable parent
599                MergeAction::AOnly
600            }
601        }
602    }
603
604    fn import_new_items(
605        parent_a: &Element,
606        elements_b_only: Vec<(Element, usize)>,
607        new_file: &WeakArxmlFile,
608        version: AutosarVersion,
609        undo: &mut Vec<MergeUndo>,
610    ) -> Result<(), AutosarDataError> {
611        // elements in elements_b_only are not present in the model yet, so they need to be added
612        for (idx, (new_element, insert_pos)) in elements_b_only.into_iter().enumerate() {
613            // idx number of elements have already been inserted, so the destination position must be adjusted
614            let dest = insert_pos + idx;
615
616            Self::import_single_item(parent_a, new_element, dest, new_file, version, undo)?;
617        }
618        Ok(())
619    }
620
621    fn import_single_item(
622        parent_a: &Element,
623        new_element: Element,
624        dest: usize,
625        new_file: &WeakArxmlFile,
626        version: AutosarVersion,
627        undo: &mut Vec<MergeUndo>,
628    ) -> Result<(), AutosarDataError> {
629        let mut parent_a_locked = parent_a.0.write();
630
631        // add the new_element (from side b) to the content of parent_a
632        // to do this, first check valid element insertion positions. Nothing may be modified
633        // before this fallible step, so that a failed import leaves new_element untouched
634        let (first_pos, last_pos) = parent_a_locked.calc_element_insert_range(new_element.element_name(), version)?;
635
636        // clamp dest, so that first_pos <= dest <= last_pos
637        let dest = dest.max(first_pos).min(last_pos);
638
639        let (old_parent, old_file_membership) = {
640            let mut new_elem_locked = new_element.0.write();
641            let old_parent = std::mem::replace(
642                &mut new_elem_locked.parent,
643                ElementOrModel::Element(parent_a.downgrade(), parent_a_locked.weak_model()),
644            );
645            // restrict new_element, it is only present in new_file
646            let old_file_membership = new_elem_locked.file_membership_cloned();
647            new_elem_locked.insert_file_membership(new_file.clone());
648            (old_parent, old_file_membership)
649        };
650        undo.push(MergeUndo::Imported {
651            new_parent: parent_a.clone(),
652            element: new_element.clone(),
653            old_parent,
654            old_file_membership,
655        });
656
657        // insert the element from b at the calculated position
658        parent_a_locked
659            .content
660            .insert(dest, ElementContent::Element(new_element));
661
662        Ok(())
663    }
664
665    // undo all merge actions recorded after the position mark, in reverse order
666    fn rollback_merge(undo: &mut Vec<MergeUndo>, mark: usize) {
667        for action in undo.drain(mark..).rev() {
668            match action {
669                MergeUndo::Imported {
670                    new_parent,
671                    element,
672                    old_parent,
673                    old_file_membership,
674                } => {
675                    // take the element out of the content of its new parent again
676                    let mut new_parent_locked = new_parent.0.write();
677                    if let Some(pos) = new_parent_locked
678                        .content
679                        .iter()
680                        .position(|item| matches!(item, ElementContent::Element(e) if *e == element))
681                    {
682                        new_parent_locked.content.remove(pos);
683                    }
684                    drop(new_parent_locked);
685                    // the element is still in the content of its original parent, so the
686                    // parent reference must point there again
687                    let mut elem_locked = element.0.write();
688                    elem_locked.set_parent(old_parent);
689                    elem_locked.set_file_membership(old_file_membership);
690                }
691                MergeUndo::FileMembership {
692                    element,
693                    old_file_membership,
694                } => {
695                    element.0.write().set_file_membership(old_file_membership);
696                }
697                MergeUndo::FileMembershipExtended { element, file } => {
698                    element.0.write().remove_file_membership(&file);
699                }
700            }
701        }
702    }
703
704    fn merge_sub_elements(
705        parent_a: &Element,
706        elements_merge: Vec<(Element, Element)>,
707        files: &HashSet<WeakArxmlFile>,
708        new_file: &WeakArxmlFile,
709        version: AutosarVersion,
710        undo: &mut Vec<MergeUndo>,
711    ) -> Result<(), AutosarDataError> {
712        for (elem_a, elem_b) in elements_merge {
713            // get the list of files that the element from a is present in
714            let files = match elem_a.0.read().file_membership.as_deref() {
715                Some(elem_files) => elem_files.clone(),
716                None => files.clone(),
717            };
718
719            // merge the two elements; remember where the actions of this merge start in the
720            // undo log, so that the merge can be rolled back if it fails
721            let undo_mark = undo.len();
722            let result = AutosarModel::merge_element(&elem_a, &files, &elem_b, new_file, undo);
723            match result {
724                Ok(()) => {
725                    // update the file membership of the merged element, if there was any
726                    let mut elem_a_locked = elem_a.0.write();
727                    if elem_a_locked.file_membership.is_some() && elem_a_locked.insert_file_membership(new_file.clone())
728                    {
729                        drop(elem_a_locked);
730                        undo.push(MergeUndo::FileMembershipExtended {
731                            element: elem_a.clone(),
732                            file: new_file.clone(),
733                        });
734                    }
735                }
736                Err(e) => {
737                    if let AutosarDataError::ElementInsertionConflict { parent_path, .. } = &e {
738                        // failed to merge sub element due to insertion conflict
739                        if elem_a.is_identifiable() {
740                            // if the merge of an identifiable element fails, then the whole merge fails
741                            return Err(AutosarDataError::InvalidFileMerge {
742                                path: parent_path.clone(),
743                            });
744                        } else if elem_a.get_sub_element(ElementName::DefinitionRef).is_some() {
745                            // elements with DefinitionRef are paired for merging based on the DefinitionRef, so if the merge fails, then the whole merge fails
746                            return Err(AutosarDataError::InvalidFileMerge {
747                                path: parent_path.clone(),
748                            });
749                        } else if parent_a.element_type().splittable_in(version) {
750                            // undo the partial merge: the failed merge_element may already have
751                            // moved sub elements of elem_b into elem_a. These must be returned to
752                            // elem_b, otherwise they would be part of both elements at once
753                            Self::rollback_merge(undo, undo_mark);
754
755                            let old_file_membership = elem_a.0.read().file_membership_cloned();
756                            elem_a.set_file_membership(files);
757                            undo.push(MergeUndo::FileMembership {
758                                element: elem_a.clone(),
759                                old_file_membership,
760                            });
761
762                            // try to import elem_b as a new item instead
763                            let dest = elem_a.position().unwrap_or_default() + 1;
764                            Self::import_single_item(parent_a, elem_b, dest, new_file, version, undo).map_err(|_| e)?;
765                            // recovery succeeded: continue with the remaining elements
766                            continue;
767                        }
768                    }
769
770                    // propagate the error back to the parent, perhaps it can be handled there
771                    return Err(e);
772                }
773            }
774        }
775        Ok(())
776    }
777
778    /// Load an arxml file
779    ///
780    /// This function is a wrapper around `load_buffer` to make the common case of loading a file from disk more convenient
781    ///
782    /// # Parameters:
783    ///
784    ///  - `filename`: the original filename of the data, or a newly generated name that is unique within the `AutosarData` instance.
785    ///  - `strict`: toggle strict parsing. Some parsing errors are recoverable and can be issued as warnings.
786    ///
787    /// # Example
788    ///
789    /// ```no_run
790    /// # use autosar_data::*;
791    /// # fn main() -> Result<(), AutosarDataError> {
792    /// let model = AutosarModel::new();
793    /// model.load_file("filename.arxml", true)?;
794    /// # Ok(())
795    /// # }
796    /// ```
797    ///
798    /// # Errors
799    ///
800    ///  - [`AutosarDataError::IoErrorRead`]: There was an error while reading the file
801    ///  - [`AutosarDataError::DuplicateFilenameError`]: The model already contains a file with this filename
802    ///  - [`AutosarDataError::OverlappingDataError`]: The new data contains Autosar paths that are already defined by the existing data
803    ///  - [`AutosarDataError::ParserError`]: The parser detected an error; the source field gives further details
804    ///
805    pub fn load_file<P: AsRef<Path>>(
806        &self,
807        filename: P,
808        strict: bool,
809    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarDataError> {
810        let filename_buf = filename.as_ref().to_path_buf();
811        let buffer = std::fs::read(&filename_buf).map_err(|err| AutosarDataError::IoErrorRead {
812            filename: filename_buf.clone(),
813            ioerror: err,
814        })?;
815
816        self.load_buffer(&buffer, &filename_buf, strict)
817    }
818
819    /// remove a file from the model
820    ///
821    /// # Parameters:
822    ///
823    ///  - `file`: The file that will be removed from the model
824    ///
825    /// # Example
826    ///
827    /// ```
828    /// # use autosar_data::*;
829    /// # fn main() -> Result<(), AutosarDataError> {
830    /// let model = AutosarModel::new();
831    /// let file = model.create_file("filename.arxml", AutosarVersion::Autosar_00050)?;
832    /// model.remove_file(&file);
833    /// # Ok(())
834    /// # }
835    /// ```
836    pub fn remove_file(&self, file: &ArxmlFile) {
837        // the file list lock is held for the whole operation, so that concurrent calls of
838        // create_file / load_buffer / remove_file cannot interleave
839        let file_list = self.file_list();
840        let mut locked_file_list = file_list.lock();
841
842        let find_result = locked_file_list
843            .iter()
844            .enumerate()
845            .find(|(_, f)| *f == file)
846            .map(|(pos, _)| pos);
847        if let Some(pos) = find_result {
848            locked_file_list.swap_remove(pos);
849            if locked_file_list.is_empty() {
850                // no other files remain in the model, so it reverts to being empty
851                let mut locked_model = self.0.write();
852                // clear the parent ref of all sub elements in case other handles to them still exist
853                for elem in locked_model.root_element.sub_elements() {
854                    elem.detach_recursive();
855                }
856                locked_model.root_element.0.write().content.clear();
857                locked_model.root_element.set_file_membership(HashSet::new());
858                locked_model.identifiables.clear();
859                locked_model.reference_origins.clear();
860                locked_model.relative_references.clear();
861            } else {
862                // other files still contribute elements, so only the elements specifically associated with this file should be removed
863                let _ = self.root_element().remove_from_file(file);
864            }
865        }
866    }
867
868    /// serialize each of the files in the model
869    ///
870    /// returns the result in a `HashMap` of <`file_name`, `file_content`>
871    ///
872    /// # Example
873    ///
874    /// ```
875    /// # use autosar_data::*;
876    /// # fn main() -> Result<(), AutosarDataError> {
877    /// let model = AutosarModel::new();
878    /// for (pathbuf, file_content) in model.serialize_files() {
879    ///     // do something with it
880    /// }
881    /// # Ok(())
882    /// # }
883    /// ```
884    ///
885    #[must_use]
886    pub fn serialize_files(&self) -> HashMap<PathBuf, String> {
887        let mut result = HashMap::new();
888        for file in self.files() {
889            if let Ok(data) = file.serialize() {
890                result.insert(file.filename(), data);
891            }
892        }
893        result
894    }
895
896    /// write all files in the model
897    ///
898    /// This is a wrapper around `serialize_files`. The current filename of each file will be used to write the serialized data.
899    ///
900    /// If any of the individual files cannot be written, then `write()` will abort and return the error.
901    /// This may result in a situation where some files have been written and others have not.
902    ///
903    /// # Example
904    ///
905    /// ```
906    /// # use autosar_data::*;
907    /// # fn main() -> Result<(), AutosarDataError> {
908    /// let model = AutosarModel::new();
909    /// // load or create files
910    /// model.write()?;
911    /// # Ok(())
912    /// # }
913    /// ```
914    ///
915    /// # Errors
916    ///
917    ///  - [`AutosarDataError::IoErrorWrite`]: There was an error while writing a file
918    pub fn write(&self) -> Result<(), AutosarDataError> {
919        for (pathbuf, filedata) in self.serialize_files() {
920            std::fs::write(pathbuf.clone(), filedata).map_err(|err| AutosarDataError::IoErrorWrite {
921                filename: pathbuf,
922                ioerror: err,
923            })?;
924        }
925        Ok(())
926    }
927
928    /// create an iterator over all [`ArxmlFile`]s in this `AutosarData` object
929    ///
930    /// # Example
931    ///
932    /// ```
933    /// # use autosar_data::*;
934    /// # fn main() -> Result<(), AutosarDataError> {
935    /// let model = AutosarModel::new();
936    /// // load or create files
937    /// for file in model.files() {
938    ///     // do something with the file
939    /// }
940    /// # Ok(())
941    /// # }
942    /// ```
943    #[must_use]
944    pub fn files(&self) -> ArxmlFileIterator {
945        ArxmlFileIterator::new(self.clone())
946    }
947
948    /// get the shared list of files in the model
949    ///
950    /// The returned mutex is held across model-lock acquisitions by `load_buffer`, `create_file`
951    /// and `remove_file`, therefore it must only be locked while holding no other lock.
952    pub(crate) fn file_list(&self) -> Arc<parking_lot::Mutex<Vec<ArxmlFile>>> {
953        self.0.read().files.clone()
954    }
955
956    /// Get a reference to the root ```<AUTOSAR ...>``` element of this model
957    ///
958    /// # Example
959    ///
960    /// ```
961    /// # use autosar_data::*;
962    /// # let model = AutosarModel::new();
963    /// # let _file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
964    /// let autosar_element = model.root_element();
965    /// ```
966    #[must_use]
967    pub fn root_element(&self) -> Element {
968        let locked_model = self.0.read();
969        locked_model.root_element.clone()
970    }
971
972    /// get a named element by its Autosar path
973    ///
974    /// This is a lookup in a hash table and runs in O(1) time
975    ///
976    /// # Parameters
977    ///
978    ///  - `path`: The Autosar path to look up
979    ///
980    /// # Example
981    ///
982    /// ```
983    /// # use autosar_data::*;
984    /// # fn main() -> Result<(), AutosarDataError> {
985    /// let model = AutosarModel::new();
986    /// // [...]
987    /// if let Some(element) = model.get_element_by_path("/Path/To/Element") {
988    ///     // use the element
989    /// }
990    /// # Ok(())
991    /// # }
992    /// ```
993    #[must_use]
994    pub fn get_element_by_path(&self, path: &str) -> Option<Element> {
995        let model = self.0.read();
996        model.identifiables.get(path).and_then(WeakElement::upgrade)
997    }
998
999    /// Duplicate the model
1000    ///
1001    /// This creates a second, fully independent model.
1002    /// The original model and the duplicate are not linked in any way and can be modified independently.
1003    ///
1004    /// # Example
1005    /// ```
1006    /// # use autosar_data::*;
1007    /// # fn main() -> Result<(), AutosarDataError> {
1008    /// let model = AutosarModel::new();
1009    /// // [...]
1010    /// let model_copy = model.duplicate()?;
1011    /// assert!(model != model_copy);
1012    /// # Ok(())
1013    /// # }
1014    /// ```
1015    ///
1016    /// # Errors
1017    ///
1018    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1019    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1020    ///    The operation was aborted to avoid a deadlock, but can be retried.
1021    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
1022    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
1023    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
1024    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1025    pub fn duplicate(&self) -> Result<AutosarModel, AutosarDataError> {
1026        let copy = Self::new();
1027        let mut filemap = HashMap::new();
1028
1029        for orig_file in self.files() {
1030            let filename = orig_file.filename();
1031            let new_file = copy.create_file(filename.clone(), orig_file.version())?;
1032            new_file.0.write().xml_standalone = orig_file.0.read().xml_standalone;
1033            filemap.insert(filename, new_file.downgrade());
1034        }
1035
1036        // by inserting copies of the sub elements of <AUTOSAR>, we automatically
1037        // get up-to-date identifiables and reference_origins
1038        for element in self.root_element().sub_elements() {
1039            let copy_root = copy.root_element();
1040            let root_weak = copy_root.downgrade();
1041            copy_root
1042                .0
1043                .write()
1044                .create_copied_sub_element_unfiltered(root_weak, &element, &copy)?;
1045        }
1046
1047        // the copies contain unresolved relative references, since a reference base can only be
1048        // resolved once the whole tree is in place
1049        copy.resolve_relative_references();
1050
1051        // `create_copied_sub_element_unfiltered` does not transfer information about file
1052        // membership, so this needs to be added back.
1053        let orig_iter = self.elements_dfs();
1054        let copy_iter = copy.elements_dfs();
1055        let combined = std::iter::zip(orig_iter, copy_iter);
1056        for ((_, orig_elem), (_, copy_elem)) in combined {
1057            // If the copy ever stopped being exact, the two iterators would run out of step and the
1058            // file membership would be written to the wrong elements, which silently moves elements
1059            // from one file to another.
1060            debug_assert_eq!(orig_elem.element_name(), copy_elem.element_name());
1061            let mut locked_copy = copy_elem.0.try_write().ok_or(AutosarDataError::ParentElementLocked)?;
1062            locked_copy.file_membership = None;
1063
1064            let orig_files = orig_elem.0.read().file_membership_cloned();
1065            for orig_file in orig_files.iter().filter_map(WeakArxmlFile::upgrade) {
1066                if let Some(copy_file) = filemap.get(&orig_file.filename()) {
1067                    locked_copy.insert_file_membership(copy_file.clone());
1068                }
1069            }
1070        }
1071
1072        Ok(copy)
1073    }
1074
1075    /// create a depth-first iterator over all [Element]s in the model
1076    ///
1077    /// The iterator returns all elements from the merged model, consisting of
1078    /// data from all arxml files loaded in this model.
1079    ///
1080    /// Directly printing the return values could show something like this:
1081    ///
1082    /// Note: If the model is modified while iterating, the iterator may skip elements or return duplicates.
1083    ///
1084    /// <pre>
1085    /// 0: AUTOSAR
1086    /// 1: AR-PACKAGES
1087    /// 2: AR-PACKAGE
1088    /// ...
1089    /// 2: AR-PACKAGE
1090    /// </pre>
1091    ///
1092    /// # Example
1093    ///
1094    /// ```
1095    /// # use autosar_data::*;
1096    /// # fn main() -> Result<(), AutosarDataError> {
1097    /// # let model = AutosarModel::new();
1098    /// for (depth, element) in model.elements_dfs() {
1099    ///     // [...]
1100    /// }
1101    /// # Ok(())
1102    /// # }
1103    /// ```
1104    #[must_use]
1105    pub fn elements_dfs(&self) -> ElementsDfsIterator {
1106        self.root_element().elements_dfs()
1107    }
1108
1109    /// Create a depth first iterator over all [Element]s in this model, up to a maximum depth
1110    ///
1111    /// The iterator returns all elements from the merged model, consisting of
1112    /// data from all arxml files loaded in this model.
1113    ///
1114    /// Note: If the model is modified while iterating, the iterator may skip elements or return duplicates.
1115    ///
1116    /// # Example
1117    ///
1118    /// ```
1119    /// # use autosar_data::*;
1120    /// # let model = AutosarModel::new();
1121    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1122    /// # let element = model.root_element();
1123    /// # element.create_sub_element(ElementName::ArPackages).unwrap();
1124    /// # let sub_elem = element.get_sub_element(ElementName::ArPackages).unwrap();
1125    /// # sub_elem.create_named_sub_element(ElementName::ArPackage, "test2").unwrap();
1126    /// for (depth, elem) in model.elements_dfs_with_max_depth(1) {
1127    ///     assert!(depth <= 1);
1128    ///     // ...
1129    /// }
1130    /// ```
1131    #[must_use]
1132    pub fn elements_dfs_with_max_depth(&self, max_depth: usize) -> ElementsDfsIterator {
1133        self.root_element().elements_dfs_with_max_depth(max_depth)
1134    }
1135
1136    /// Recursively sort all elements in the model. This is exactly identical to calling `sort()` on the root element of the model.
1137    ///
1138    /// All sub elements of the root element are sorted alphabetically.
1139    /// If the sub-elements are named, then the sorting is performed according to the item names,
1140    /// otherwise the serialized form of the sub-elements is used for sorting.
1141    ///
1142    /// Element attributes are not taken into account while sorting.
1143    /// The elements are sorted in place, and sorting cannot fail, so there is no return value.
1144    ///
1145    /// # Example
1146    /// ```
1147    /// # use autosar_data::*;
1148    /// # let model = AutosarModel::new();
1149    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1150    /// model.sort();
1151    /// ```
1152    pub fn sort(&self) {
1153        self.root_element().sort();
1154    }
1155
1156    /// Create an iterator over the list of the Autosar paths of all identifiable elements
1157    ///
1158    /// The list contains the full Autosar path of each element. It is not sorted.
1159    ///
1160    /// Note: If the model is modified while iterating, the iterator may skip elements or return duplicates.
1161    ///
1162    /// # Example
1163    ///
1164    /// ```
1165    /// # use autosar_data::*;
1166    /// # fn main() -> Result<(), AutosarDataError> {
1167    /// # let model = AutosarModel::new();
1168    /// for (path, _) in model.identifiable_elements() {
1169    ///     let element = model.get_element_by_path(&path).unwrap();
1170    ///     // [...]
1171    /// }
1172    /// # Ok(())
1173    /// # }
1174    /// ```
1175    #[must_use]
1176    pub fn identifiable_elements(&self) -> IdentifiablesIterator {
1177        IdentifiablesIterator::new(self)
1178    }
1179
1180    /// return all elements referring to the given target path
1181    ///
1182    /// It returns [`WeakElement`]s which must be upgraded to get usable [Element]s.
1183    ///
1184    /// This is effectively the reverse operation of `get_element_by_path()`
1185    ///
1186    /// # Parameters
1187    ///
1188    ///  - `target_path`: The path whose references should be returned
1189    ///
1190    /// # Example
1191    ///
1192    /// ```
1193    /// # use autosar_data::*;
1194    /// # fn main() -> Result<(), AutosarDataError> {
1195    /// # let model = AutosarModel::new();
1196    /// for weak_element in model.get_references_to("/Path/To/Element") {
1197    ///     // [...]
1198    /// }
1199    /// # Ok(())
1200    /// # }
1201    /// ```
1202    #[must_use]
1203    pub fn get_references_to(&self, target_path: &str) -> Vec<WeakElement> {
1204        let locked_model = self.0.read();
1205        // relative references are registered under the path of their target as well, so a single lookup
1206        // finds both kinds
1207        locked_model
1208            .reference_origins
1209            .get(target_path)
1210            .cloned()
1211            .unwrap_or_default()
1212    }
1213
1214    /// check all Autosar path references and return a list of elements with invalid references
1215    ///
1216    /// For each reference: The target must exist and the DEST attribute must correctly specify the type of the target
1217    ///
1218    /// Relative references are checked as well: their BASE attribute must name a REFERENCE-BASE
1219    /// which is in scope, i.e. declared by the package containing the reference or by one of its
1220    /// ancestor packages, and the relative path must lead to an existing element from there.
1221    ///
1222    /// If no references are invalid, then the return value is an empty list
1223    ///
1224    /// # Example
1225    /// ```
1226    /// # use autosar_data::*;
1227    /// # fn main() -> Result<(), AutosarDataError> {
1228    /// # let model = AutosarModel::new();
1229    /// for broken_ref_weak in model.check_references() {
1230    ///     if let Some(broken_ref) = broken_ref_weak.upgrade() {
1231    ///         // update or delete ref?
1232    ///     }
1233    /// }
1234    /// # Ok(())
1235    /// # }
1236    /// ```
1237    #[must_use]
1238    pub fn check_references(&self) -> Vec<WeakElement> {
1239        let mut broken_refs = Vec::new();
1240
1241        let model = self.0.read();
1242        for (path, element_list) in &model.reference_origins {
1243            if let Some(target_elem_weak) = model.identifiables.get(path) {
1244                // reference target exists
1245                if let Some(target_elem) = target_elem_weak.upgrade() {
1246                    // the target of the reference exists, but the reference can still be technically invalid
1247                    // if the content of the DEST attribute on the reference is wrong
1248                    for referring_elem_weak in element_list {
1249                        if let Some(referring_elem) = referring_elem_weak.upgrade() {
1250                            if let Some(CharacterData::Enum(dest_value)) =
1251                                referring_elem.attribute_value(AttributeName::Dest)
1252                            {
1253                                if !target_elem.element_type().verify_reference_dest(dest_value) {
1254                                    // wrong reference type in the DEST attribute
1255                                    broken_refs.push(referring_elem_weak.clone());
1256                                }
1257                            } else {
1258                                // DEST attribute does not exist - can only happen if broken data was loaded with strict == false
1259                                broken_refs.push(referring_elem_weak.clone());
1260                            }
1261                        }
1262                    }
1263                } else {
1264                    // The strong ref count of target_elem can only go to zero if the element is removed,
1265                    // but remove_element() should also update data.identifiables and data.reference_origins.
1266                    broken_refs.extend(element_list.iter().cloned());
1267                }
1268            } else {
1269                // reference target does not exist
1270                broken_refs.extend(element_list.iter().cloned());
1271            }
1272        }
1273
1274        // A relative reference whose base is not in scope has no target path at all, so it is not part
1275        // of reference_origins and the loop above cannot report it.
1276        for (referring_elem_weak, target_path) in &model.relative_references {
1277            if target_path.is_none() && referring_elem_weak.upgrade().is_some() {
1278                broken_refs.push(referring_elem_weak.clone());
1279            }
1280        }
1281
1282        broken_refs
1283    }
1284
1285    /// Get the [`AutosarVersion`] of the only file of this model, if it has exactly one file
1286    ///
1287    /// Every element of such a model belongs to that one file, which lets `Element::min_version()`
1288    /// skip the search for the file membership of the element.
1289    pub(crate) fn single_file_version(&self) -> Option<AutosarVersion> {
1290        let file_list = self.file_list();
1291        let locked_file_list = file_list.lock();
1292        match locked_file_list.as_slice() {
1293            [file] => Some(file.version()),
1294            _ => None,
1295        }
1296    }
1297
1298    /// Create a weak reference to this data
1299    pub(crate) fn downgrade(&self) -> WeakAutosarModel {
1300        WeakAutosarModel(Arc::downgrade(&self.0))
1301    }
1302
1303    /// Add an identifiable element to the cache
1304    pub(crate) fn add_identifiable(&self, new_path: String, elem: WeakElement) {
1305        let mut model = self.0.write();
1306        model.identifiables.insert(new_path, elem);
1307    }
1308
1309    /// Fix the caches after one or more subtrees of elements have been renamed or moved.
1310    ///
1311    /// This updates the keys of the `identifiables` map
1312    pub(crate) fn fix_element_paths(&self, remap: &PathRemap) {
1313        if remap.is_noop() {
1314            return;
1315        }
1316        let mut model = self.0.write();
1317
1318        // the renamed element might contain other identifiable elements that are affected by the renaming
1319        let keys: Vec<String> = model.identifiables.keys().cloned().collect();
1320        for key in keys {
1321            // find keys referring to entries inside the renamed/moved subtree
1322            if let Some(new_key) = remap.map(&key) {
1323                // fix the identifiables hashmap
1324                if let Some(entry) = model.identifiables.swap_remove(&key) {
1325                    model.identifiables.insert(new_key, entry);
1326                }
1327            }
1328        }
1329    }
1330
1331    /// Fix the caches which record reference targets, after one or more subtrees of
1332    /// elements have been renamed or moved, and update the referring elements to match.
1333    ///
1334    /// This updates the keys of the `reference_origins` map, as well as the character data of each
1335    /// referring element, so that both absolute and relative references follow their target. The
1336    /// entries of `relative_references` are updated to the new keys.
1337    pub(crate) fn fix_reference_paths(
1338        &self,
1339        remap: &PathRemap,
1340        version: AutosarVersion,
1341    ) -> Result<(), AutosarDataError> {
1342        if remap.is_noop() {
1343            return Ok(());
1344        }
1345        let mut model = self.0.write();
1346
1347        // Check all references and update those that point to a renamed/moved element. Since the key is
1348        // the path of the target, this applies to relative references in exactly the same way; only the
1349        // character data that has to be written back differs between the two.
1350        let refpaths = model.reference_origins.keys().cloned().collect::<Vec<String>>();
1351        for refpath in refpaths {
1352            // if the existing reference points into a renamed/moved subtree, then it needs to be updated
1353            let Some(refpath_new) = remap.map(&refpath).filter(|new| *new != refpath) else {
1354                continue;
1355            };
1356            let Some(reflist) = model.reference_origins.remove(&refpath) else {
1357                continue;
1358            };
1359            let mut updated = Vec::with_capacity(reflist.len());
1360            let mut unchanged = Vec::new();
1361            for weak_ref_elem in reflist {
1362                let is_relative = model.relative_references.contains_key(&weak_ref_elem);
1363                let new_content = match weak_ref_elem.upgrade() {
1364                    Some(ref_elem) => {
1365                        let new_content = if is_relative {
1366                            model.fix_relative_reference_content(&ref_elem, &refpath, &refpath_new, remap)
1367                        } else {
1368                            Some(refpath_new.clone())
1369                        };
1370                        if let Some(new_content) = &new_content {
1371                            // can't use Element::set_character_data() here, because the model is locked
1372                            ref_elem.0.write().set_character_data(new_content.clone(), version)?;
1373                        }
1374                        new_content
1375                    }
1376                    // the element is gone; the entry follows the key so that it is pruned in one place
1377                    None => Some(refpath_new.clone()),
1378                };
1379                if new_content.is_some() {
1380                    if is_relative {
1381                        model
1382                            .relative_references
1383                            .insert(weak_ref_elem.clone(), Some(refpath_new.clone()));
1384                    }
1385                    updated.push(weak_ref_elem);
1386                } else {
1387                    // The new target cannot be expressed relative to this reference base, so the
1388                    // character data is left alone and the entry keeps its old key. The operation which
1389                    // moved the target out of the base is responsible for calling
1390                    // resolve_relative_references() to settle what the reference now points at.
1391                    unchanged.push(weak_ref_elem);
1392                }
1393            }
1394            if !updated.is_empty() {
1395                model.reference_origins.insert(refpath_new, updated);
1396            }
1397            if !unchanged.is_empty() {
1398                model.reference_origins.insert(refpath, unchanged);
1399            }
1400        }
1401
1402        Ok(())
1403    }
1404
1405    // remove a deleted element from the cache
1406    pub(crate) fn remove_identifiable(&self, path: &str) {
1407        let mut model = self.0.write();
1408        model.identifiables.swap_remove(path);
1409    }
1410
1411    /// Register a reference element
1412    ///
1413    /// `new_ref` is the character data of the reference and `base` its BASE attribute. An absolute
1414    /// reference is registered under its character data, which is already the path of its target.
1415    ///
1416    /// A relative reference cannot be resolved here: this is also called while element locks are held,
1417    /// and resolving a reference base means reading the element tree. It is therefore only recorded as
1418    /// unresolved, and [`Self::resolve_relative_references`] computes its target path afterwards.
1419    pub(crate) fn add_reference_origin(&self, new_ref: &str, base: Option<&str>, origin: WeakElement) {
1420        let mut data = self.0.write();
1421        if base.is_some() {
1422            data.relative_references.insert(origin, None);
1423        } else {
1424            data.reference_origins
1425                .entry(new_ref.to_owned())
1426                .or_default()
1427                .push(origin);
1428        }
1429    }
1430
1431    /// De-register a reference element
1432    ///
1433    /// `reference` is the character data of the reference, which is the key of an absolute reference.
1434    /// The key of a relative reference is taken from `relative_references` instead: it cannot be
1435    /// recomputed here, because this is also called while element locks are held and after the element
1436    /// has been detached from the tree, when its reference base can no longer be resolved.
1437    pub(crate) fn remove_reference_origin(&self, reference: &str, element: WeakElement) {
1438        let mut data = self.0.write();
1439        let key = match data.relative_references.remove(&element) {
1440            Some(relative_key) => relative_key,
1441            None => Some(reference.to_owned()),
1442        };
1443        if let Some(key) = key {
1444            data.remove_reference_origin_by_key(&key, &element);
1445        }
1446    }
1447
1448    /// Move a reference element to a different key, after its character data or BASE attribute changed
1449    ///
1450    /// A relative reference is left unresolved, exactly as in [`Self::add_reference_origin`].
1451    pub(crate) fn fix_reference_origins(
1452        &self,
1453        old_ref: &str,
1454        new_ref: &str,
1455        new_base: Option<&str>,
1456        origin: WeakElement,
1457    ) {
1458        self.remove_reference_origin(old_ref, origin.clone());
1459        self.add_reference_origin(new_ref, new_base, origin);
1460    }
1461
1462    /// Compute the target path of every relative reference and update the caches to match
1463    ///
1464    /// This must be called by any operation which can change what a relative reference resolves to:
1465    /// the reference itself was created or modified, it moved to a package where a different reference
1466    /// base is in scope, or a REFERENCE-BASE declaration changed. It is idempotent, and free for the
1467    /// common case of a model which contains no relative references at all.
1468    ///
1469    /// It must not be called while an element lock is held, since resolving a reference base reads the
1470    /// element tree.
1471    pub(crate) fn resolve_relative_references(&self) {
1472        let mut model = self.0.write();
1473        if model.relative_references.is_empty() {
1474            return;
1475        }
1476        let origins: Vec<WeakElement> = model.relative_references.keys().cloned().collect();
1477        for origin in origins {
1478            let new_key = origin
1479                .upgrade()
1480                .and_then(|origin_elem| origin_elem.resolve_relative_target());
1481            let old_key = model
1482                .relative_references
1483                .insert(origin.clone(), new_key.clone())
1484                .flatten();
1485            if old_key == new_key {
1486                continue;
1487            }
1488            if let Some(old_key) = old_key {
1489                model.remove_reference_origin_by_key(&old_key, &origin);
1490            }
1491            match new_key {
1492                Some(new_key) => model.reference_origins.entry(new_key).or_default().push(origin),
1493                // the reference base is not in scope: the reference has no target path, and the entry
1494                // is only kept so that check_references() can report it
1495                None => {
1496                    if origin.upgrade().is_none() {
1497                        // the element is gone, so the entry is of no use to anyone
1498                        model.relative_references.remove(&origin);
1499                    }
1500                }
1501            }
1502        }
1503    }
1504
1505    /// Get the absolute path that a relative reference currently resolves to
1506    ///
1507    /// The result is `None` if `element` is not a registered relative reference, or if its reference
1508    /// base is not in scope, in which case it has no target path at all.
1509    pub(crate) fn relative_reference_target(&self, element: &WeakElement) -> Option<String> {
1510        self.0.read().relative_references.get(element).cloned().flatten()
1511    }
1512}
1513
1514impl AutosarModelRaw {
1515    pub(crate) fn wrap(self) -> AutosarModel {
1516        AutosarModel(Arc::new(RwLock::new(self)))
1517    }
1518
1519    /// Rewrite the character data of a relative reference after its target moved from `old_target` to
1520    /// `new_target`, so that it still refers to the same element. The new character data is returned.
1521    ///
1522    /// The path of the reference base is recovered from the old target path and the old character data,
1523    /// so nothing has to be resolved here. The result is `None` if the new target cannot be expressed
1524    /// relative to the same reference base, which means the reference cannot follow its target.
1525    fn fix_relative_reference_content(
1526        &mut self,
1527        element: &Element,
1528        old_target: &str,
1529        new_target: &str,
1530        remap: &PathRemap,
1531    ) -> Option<String> {
1532        let old_content = element.character_data()?.string_value()?;
1533        // old_target is the base path followed by '/' and the character data, so what remains after
1534        // removing those is the path of the reference base
1535        let base_path = old_target.strip_suffix(&old_content)?.strip_suffix('/')?;
1536        // the reference base may have been renamed or moved as well
1537        let new_base_path = remap.map(base_path);
1538        let new_base_path = new_base_path.as_deref().unwrap_or(base_path);
1539        let new_content = replace_path_prefix(new_target, new_base_path, "")?
1540            .strip_prefix('/')?
1541            .to_owned();
1542
1543        Some(new_content)
1544    }
1545
1546    /// remove `element` from the list of referring elements registered for the target path `key`
1547    fn remove_reference_origin_by_key(&mut self, key: &str, element: &WeakElement) {
1548        if let Some(origins) = self.reference_origins.get_mut(key) {
1549            if let Some(index) = origins.iter().position(|origin| origin == element) {
1550                origins.swap_remove(index);
1551            }
1552            if origins.is_empty() {
1553                self.reference_origins.remove(key);
1554            }
1555        }
1556    }
1557}
1558
1559impl std::fmt::Debug for AutosarModel {
1560    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1561        // the file list mutex must not be locked while the model lock is held, so the list is cloned first
1562        let files = self.file_list().lock().clone();
1563        let model = self.0.read();
1564        // instead of the usual f.debug_struct().field().field() ...
1565        // this is disassembled here, in order to hold self.0.lock() as briefly as possible
1566        let rootelem = model.root_element.clone();
1567        let mut dbgstruct = f.debug_struct("AutosarModel");
1568        dbgstruct.field("root_element", &rootelem);
1569        dbgstruct.field("files", &files);
1570        dbgstruct.field("identifiables", &model.identifiables);
1571        dbgstruct.field("reference_origins", &model.reference_origins);
1572        dbgstruct.field("relative_references", &model.relative_references);
1573        dbgstruct.finish()
1574    }
1575}
1576
1577impl Default for AutosarModel {
1578    fn default() -> Self {
1579        Self::new()
1580    }
1581}
1582
1583impl PartialEq for AutosarModel {
1584    fn eq(&self, other: &Self) -> bool {
1585        Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
1586    }
1587}
1588
1589impl Eq for AutosarModel {}
1590
1591impl Hash for AutosarModel {
1592    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1593        state.write_usize(Arc::as_ptr(&self.0) as usize);
1594    }
1595}
1596
1597impl WeakAutosarModel {
1598    pub(crate) fn upgrade(&self) -> Option<AutosarModel> {
1599        Weak::upgrade(&self.0).map(AutosarModel)
1600    }
1601}
1602
1603impl std::fmt::Debug for WeakAutosarModel {
1604    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1605        f.write_fmt(format_args!("AutosarModel:WeakRef {:p}", Weak::as_ptr(&self.0)))
1606    }
1607}
1608
1609/// Verification of the model-level caches, for use by the tests
1610///
1611/// Every one of these caches is derived from the element tree, so each mutating operation has to keep
1612/// them in agreement with it. `check_references` cannot serve as that check: it only reports cache
1613/// entries which no longer resolve, and never notices a reference which exists in the tree but is
1614/// missing from the cache.
1615#[cfg(test)]
1616impl AutosarModel {
1617    /// Check the per-element invariants that every mutating operation has to maintain
1618    ///
1619    /// Each element caches the model it belongs to next to its parent reference, so every operation
1620    /// that attaches a subtree to a model, or moves one from another model, has to update the whole
1621    /// subtree. An element also may not store an empty file membership, which is expressed as
1622    /// `None` instead.
1623    fn verify_element_invariants(&self) -> Result<(), String> {
1624        for (_, element) in self.root_element().elements_dfs() {
1625            match element.model() {
1626                Ok(model) if model == *self => {}
1627                Ok(_) => return Err(format!("element {} points at a different model", element.xml_path())),
1628                Err(error) => return Err(format!("element {} has no model: {error}", element.xml_path())),
1629            }
1630            // an element that is in no file at all is meaningless: an empty set has to be stored as
1631            // None, otherwise the is_none() check for "inherits its file membership" is wrong
1632            if element
1633                .0
1634                .read()
1635                .file_membership
1636                .as_deref()
1637                .is_some_and(HashSet::is_empty)
1638            {
1639                return Err(format!(
1640                    "element {} stores an empty file membership instead of None",
1641                    element.xml_path()
1642                ));
1643            }
1644        }
1645        Ok(())
1646    }
1647
1648    /// Check that the reference caches agree with the element tree
1649    ///
1650    /// The `Err` describes the first inconsistency that was found. Dead `WeakElement`s in the caches
1651    /// are ignored, since such entries are only pruned when they happen to be encountered.
1652    // clippy::mutable_key_type fires for the HashSet<Element>, because an Element contains a lock.
1653    // Hash and Eq of Element are both defined in terms of the pointer, so interior mutability cannot
1654    // affect them.
1655    #[allow(clippy::mutable_key_type)]
1656    pub(crate) fn verify_reference_caches(&self) -> Result<(), String> {
1657        // not caches of the model, but subject to the same rule that every mutating operation has
1658        // to keep them consistent, and checked here so that they are covered by the same tests
1659        self.verify_element_invariants()?;
1660
1661        let mut tree_elements = std::collections::HashSet::new();
1662        // (character data, element) of each reference without a BASE attribute
1663        let mut absolute_refs: Vec<(String, Element)> = Vec::new();
1664        // each reference with a BASE attribute, i.e. each relative reference
1665        let mut relative_refs: Vec<Element> = Vec::new();
1666
1667        // collect what the element tree says
1668        for (_, element) in self.root_element().elements_dfs() {
1669            tree_elements.insert(element.clone());
1670            if element.is_reference()
1671                && let Some(CharacterData::String(text)) = element.character_data()
1672            {
1673                if element.attribute_value(AttributeName::Base).is_some() {
1674                    relative_refs.push(element.clone());
1675                } else {
1676                    absolute_refs.push((text, element.clone()));
1677                }
1678            }
1679        }
1680
1681        let model = self.0.read();
1682
1683        // an absolute reference is registered under its own character data
1684        for (text, element) in &absolute_refs {
1685            if model.relative_references.contains_key(&element.downgrade()) {
1686                return Err(format!(
1687                    "{} has no BASE attribute, but is registered as a relative reference",
1688                    element.xml_path()
1689                ));
1690            }
1691            if !model
1692                .reference_origins
1693                .get(text)
1694                .is_some_and(|origins| origins.contains(&element.downgrade()))
1695            {
1696                return Err(format!(
1697                    "{} references \"{text}\", but is missing from reference_origins[\"{text}\"]",
1698                    element.xml_path()
1699                ));
1700            }
1701        }
1702
1703        // a relative reference is registered under the path it resolves to, which the reverse index has
1704        // to agree with
1705        for element in &relative_refs {
1706            let expected_target = element.resolve_relative_target();
1707            let Some(cached_target) = model.relative_references.get(&element.downgrade()) else {
1708                return Err(format!(
1709                    "the relative reference {} is missing from relative_references",
1710                    element.xml_path()
1711                ));
1712            };
1713            if *cached_target != expected_target {
1714                return Err(format!(
1715                    "the relative reference {} resolves to {expected_target:?}, but relative_references says {cached_target:?}",
1716                    element.xml_path()
1717                ));
1718            }
1719            if let Some(target) = cached_target
1720                && !model
1721                    .reference_origins
1722                    .get(target)
1723                    .is_some_and(|origins| origins.contains(&element.downgrade()))
1724            {
1725                return Err(format!(
1726                    "the relative reference {} resolves to \"{target}\", but is missing from reference_origins[\"{target}\"]",
1727                    element.xml_path()
1728                ));
1729            }
1730        }
1731
1732        // ... and every cache entry must describe a reference which is still in the tree
1733        for (key, origins) in &model.reference_origins {
1734            for weak_origin in origins {
1735                let Some(element) = weak_origin.upgrade() else {
1736                    continue;
1737                };
1738                if !tree_elements.contains(&element) {
1739                    return Err(format!(
1740                        "reference_origins[\"{key}\"] contains {}, which is not in the element tree",
1741                        element.xml_path()
1742                    ));
1743                }
1744                let registered_correctly = match model.relative_references.get(weak_origin) {
1745                    // a relative reference: the reverse index must name this key
1746                    Some(target) => target.as_deref() == Some(key.as_str()),
1747                    // an absolute reference: its character data must be this key
1748                    None => absolute_refs.iter().any(|(text, elem)| elem == &element && text == key),
1749                };
1750                if !registered_correctly {
1751                    return Err(format!(
1752                        "reference_origins[\"{key}\"] contains {}, whose reference is {:?} with BASE={:?} and reverse index entry {:?}",
1753                        element.xml_path(),
1754                        element.character_data().and_then(|cdata| cdata.string_value()),
1755                        element
1756                            .attribute_value(AttributeName::Base)
1757                            .and_then(|cdata| cdata.string_value()),
1758                        model.relative_references.get(weak_origin)
1759                    ));
1760                }
1761            }
1762        }
1763        for weak_origin in model.relative_references.keys() {
1764            let Some(element) = weak_origin.upgrade() else {
1765                continue;
1766            };
1767            if !relative_refs.contains(&element) {
1768                return Err(format!(
1769                    "relative_references contains {}, which is not a relative reference in the element tree",
1770                    element.xml_path()
1771                ));
1772            }
1773        }
1774
1775        Ok(())
1776    }
1777}
1778
1779#[cfg(test)]
1780mod test {
1781    use super::*;
1782    use tempfile::tempdir;
1783
1784    #[test]
1785    fn create_file() {
1786        let model = AutosarModel::new();
1787        let file = model.create_file("test", AutosarVersion::Autosar_00050);
1788        assert!(file.is_ok());
1789        // error: duplicate file name
1790        let file = model.create_file("test", AutosarVersion::Autosar_00050);
1791        assert!(file.is_err());
1792
1793        // the duplicate check also works when the file name is given as a Path / PathBuf
1794        let filename = PathBuf::from("test");
1795        let file = model.create_file(&filename, AutosarVersion::Autosar_00050);
1796        assert!(matches!(
1797            file,
1798            Err(AutosarDataError::DuplicateFilenameError { verb: "create", .. })
1799        ));
1800        let file = model.create_file(Path::new("test2"), AutosarVersion::Autosar_00050);
1801        assert!(file.is_ok());
1802    }
1803
1804    #[test]
1805    fn concurrent_load_buffer() {
1806        fn make_buf(pkg: &str) -> Vec<u8> {
1807            format!(
1808                r#"<?xml version="1.0" encoding="utf-8"?>
1809            <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1810            <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>{pkg}</SHORT-NAME></AR-PACKAGE></AR-PACKAGES>
1811            </AUTOSAR>"#
1812            )
1813            .into_bytes()
1814        }
1815        // two concurrent load_buffer calls into an empty model must not interleave;
1816        // without serialization one of the two element trees could be silently lost
1817        for _ in 0..100 {
1818            let model = AutosarModel::new();
1819            let (m1, m2) = (model.clone(), model.clone());
1820            let t1 = std::thread::spawn(move || m1.load_buffer(&make_buf("PkgA"), "file1.arxml", true).is_ok());
1821            let t2 = std::thread::spawn(move || m2.load_buffer(&make_buf("PkgB"), "file2.arxml", true).is_ok());
1822            assert!(t1.join().unwrap());
1823            assert!(t2.join().unwrap());
1824            assert_eq!(model.files().count(), 2);
1825            assert!(model.get_element_by_path("/PkgA").is_some());
1826            assert!(model.get_element_by_path("/PkgB").is_some());
1827        }
1828
1829        // two concurrent loads of the same filename: exactly one of them must succeed,
1830        // even though the duplicate check before parsing cannot see the other load yet
1831        for _ in 0..100 {
1832            let model = AutosarModel::new();
1833            let (m1, m2) = (model.clone(), model.clone());
1834            let t1 = std::thread::spawn(move || m1.load_buffer(&make_buf("PkgA"), "file1.arxml", true).is_ok());
1835            let t2 = std::thread::spawn(move || m2.load_buffer(&make_buf("PkgB"), "file1.arxml", true).is_ok());
1836            let ok1 = t1.join().unwrap();
1837            let ok2 = t2.join().unwrap();
1838            assert!(ok1 != ok2, "exactly one of the two loads must succeed");
1839            assert_eq!(model.files().count(), 1);
1840        }
1841    }
1842
1843    #[test]
1844    fn load_buffer() {
1845        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1846        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1847        <AR-PACKAGES>
1848          <AR-PACKAGE>
1849            <SHORT-NAME>Pkg</SHORT-NAME>
1850            <ELEMENTS>
1851              <SYSTEM><SHORT-NAME>Thing</SHORT-NAME></SYSTEM>
1852            </ELEMENTS>
1853          </AR-PACKAGE>
1854        </AR-PACKAGES></AUTOSAR>"#;
1855        const FILEBUF2: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1856        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1857        <AR-PACKAGES>
1858          <AR-PACKAGE><SHORT-NAME>OtherPkg</SHORT-NAME></AR-PACKAGE>
1859        </AR-PACKAGES></AUTOSAR>"#;
1860        const FILEBUF3: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1861        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1862        <AR-PACKAGES>
1863          <AR-PACKAGE>
1864            <SHORT-NAME>Pkg</SHORT-NAME>
1865            <ELEMENTS>
1866            <APPLICATION-PRIMITIVE-DATA-TYPE><SHORT-NAME>Thing</SHORT-NAME></APPLICATION-PRIMITIVE-DATA-TYPE>
1867            </ELEMENTS>
1868          </AR-PACKAGE>
1869        </AR-PACKAGES></AUTOSAR>"#;
1870        const NON_ARXML: &str = "The quick brown fox jumps over the lazy dog";
1871        let model = AutosarModel::new();
1872        // succefully load a buffer
1873        let result = model.load_buffer(FILEBUF.as_bytes(), "test", true);
1874        assert!(result.is_ok());
1875        // succefully load a second buffer
1876        let result = model.load_buffer(FILEBUF2.as_bytes(), "other", true);
1877        assert!(result.is_ok());
1878        // error: duplicate file name
1879        let result = model.load_buffer(FILEBUF.as_bytes(), "test", true);
1880        assert!(result.is_err());
1881        // error: overlapping autosar paths
1882        let result = model.load_buffer(FILEBUF3.as_bytes(), "test2", true);
1883        assert!(result.is_err());
1884        // error: not arxml data
1885        let result = model.load_buffer(NON_ARXML.as_bytes(), "nonsense", true);
1886        assert!(result.is_err());
1887    }
1888
1889    #[test]
1890    fn load_file() {
1891        let dir = tempdir().unwrap();
1892
1893        let model = AutosarModel::new();
1894        let filename = dir.path().with_file_name("nonexistent.arxml");
1895        assert!(model.load_file(&filename, true).is_err());
1896
1897        let filename = dir.path().with_file_name("test.arxml");
1898        model.create_file(&filename, AutosarVersion::LATEST).unwrap();
1899        model
1900            .root_element()
1901            .create_sub_element(ElementName::ArPackages)
1902            .and_then(|ap| ap.create_named_sub_element(ElementName::ArPackage, "Pkg"))
1903            .unwrap();
1904        model.write().unwrap();
1905
1906        assert!(filename.exists());
1907
1908        // careate a new model without data
1909        let model = AutosarModel::new();
1910        model.load_file(&filename, true).unwrap();
1911        let el_pkg = model.get_element_by_path("/Pkg");
1912        assert!(el_pkg.is_some());
1913    }
1914
1915    #[test]
1916    fn data_merge() {
1917        const FILEBUF1: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1918        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1919        <AR-PACKAGES>
1920          <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
1921            <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
1922              <SHORT-NAME>BswModuleValues</SHORT-NAME>
1923              <PARAMETER-VALUES>
1924                <ECUC-NUMERICAL-PARAM-VALUE>
1925                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
1926                </ECUC-NUMERICAL-PARAM-VALUE>
1927                <ECUC-NUMERICAL-PARAM-VALUE>
1928                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
1929                </ECUC-NUMERICAL-PARAM-VALUE>
1930                <ECUC-NUMERICAL-PARAM-VALUE>
1931                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_C</DEFINITION-REF>
1932                </ECUC-NUMERICAL-PARAM-VALUE>
1933              </PARAMETER-VALUES>
1934            </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
1935          </ELEMENTS></AR-PACKAGE>
1936          <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
1937          <AR-PACKAGE><SHORT-NAME>Pkg_C</SHORT-NAME></AR-PACKAGE>
1938        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
1939        const FILEBUF2: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1940        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1941        <AR-PACKAGES>
1942          <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
1943          <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
1944            <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
1945              <SHORT-NAME>BswModuleValues</SHORT-NAME>
1946              <PARAMETER-VALUES>
1947                <ECUC-NUMERICAL-PARAM-VALUE>
1948                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
1949                </ECUC-NUMERICAL-PARAM-VALUE>
1950                <ECUC-NUMERICAL-PARAM-VALUE>
1951                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
1952                </ECUC-NUMERICAL-PARAM-VALUE>
1953              </PARAMETER-VALUES>
1954            </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
1955          </ELEMENTS></AR-PACKAGE>
1956        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
1957        // test with re-ordered identifiable elements and re-ordered BSW parameter values
1958        // file2 is a subset of file1, so the total number of elements does not increase
1959        let model = AutosarModel::new();
1960        let (file1, _) = model.load_buffer(FILEBUF1, "test1", true).unwrap();
1961        let file1_elemcount = file1.elements_dfs().count();
1962        let (file2, _) = model.load_buffer(FILEBUF2, "test2", true).unwrap();
1963        let file2_elemcount = file2.elements_dfs().count();
1964        let model_elemcount = model.elements_dfs().count();
1965        assert_eq!(file1_elemcount, model_elemcount);
1966        assert!(file1_elemcount > file2_elemcount);
1967        // verify file membership after merging
1968        let (local, fileset) = model.root_element().file_membership().unwrap();
1969        assert!(local);
1970        assert_eq!(fileset.len(), 2);
1971
1972        let el_pkg_c = model.get_element_by_path("/Pkg_C").unwrap();
1973        let (local, fileset) = el_pkg_c.file_membership().unwrap();
1974        assert!(local);
1975        assert_eq!(fileset.len(), 1);
1976        let el_npv2 = model
1977            .get_element_by_path("/Pkg_A/BswModule/BswModuleValues")
1978            .and_then(|bmv| bmv.get_sub_element(ElementName::ParameterValues))
1979            .and_then(|pv| pv.get_sub_element_at(2))
1980            .unwrap();
1981        let (loc, fm) = el_npv2.file_membership().unwrap();
1982        assert!(loc);
1983        assert_eq!(fm.len(), 1);
1984
1985        // the following two files diverge on the TIMING-RESOURCE element
1986        // this is not permitted, because SYSTEM-TIMING is not splittable
1987        const ERRFILE1: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1988        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
1989        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
1990          <ELEMENTS>
1991            <SYSTEM-TIMING>
1992              <SHORT-NAME>SystemTimings</SHORT-NAME>
1993              <CATEGORY>CAT</CATEGORY>
1994              <TIMING-RESOURCE>
1995                <SHORT-NAME>Name_One</SHORT-NAME>
1996              </TIMING-RESOURCE>
1997            </SYSTEM-TIMING>
1998          </ELEMENTS>
1999        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
2000        const ERRFILE2: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2001        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2002        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
2003          <ELEMENTS>
2004            <SYSTEM-TIMING>
2005              <SHORT-NAME>SystemTimings</SHORT-NAME>
2006              <TIMING-RESOURCE>
2007                <SHORT-NAME>Name_Two</SHORT-NAME>
2008              </TIMING-RESOURCE>
2009            </SYSTEM-TIMING>
2010          </ELEMENTS>
2011        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
2012        let model = AutosarModel::new();
2013        let result = model.load_buffer(ERRFILE1, "test1", true);
2014        assert!(result.is_ok());
2015        let result = model.load_buffer(ERRFILE2, "test2", true);
2016        let error = result.unwrap_err();
2017        assert!(matches!(error, AutosarDataError::InvalidFileMerge { .. }));
2018
2019        // diverging files, where each file uses a different element from a Choice set.
2020        // In this case the COMPU-SCALE in ERRFILE3 uses COMPU-CONST while ERRFILE4 uses COMPU-RATIONAL-COEFFS.
2021        // This is not permitted, because the COMPU-SCALE can only contain one or the other.
2022        const ERRFILE3: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2023        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2024        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
2025          <ELEMENTS>
2026            <COMPU-METHOD><SHORT-NAME>compu</SHORT-NAME>
2027              <COMPU-INTERNAL-TO-PHYS>
2028                <COMPU-SCALES>
2029                  <COMPU-SCALE><COMPU-CONST></COMPU-CONST></COMPU-SCALE>
2030                </COMPU-SCALES>
2031              </COMPU-INTERNAL-TO-PHYS>
2032            </COMPU-METHOD>
2033          </ELEMENTS>
2034        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
2035        const ERRFILE4: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2036        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2037        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
2038          <ELEMENTS>
2039            <COMPU-METHOD><SHORT-NAME>compu</SHORT-NAME>
2040              <COMPU-INTERNAL-TO-PHYS>
2041                <COMPU-SCALES>
2042                  <COMPU-SCALE><COMPU-RATIONAL-COEFFS></COMPU-RATIONAL-COEFFS></COMPU-SCALE>
2043                </COMPU-SCALES>
2044              </COMPU-INTERNAL-TO-PHYS>
2045            </COMPU-METHOD>
2046          </ELEMENTS>
2047        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
2048        let model = AutosarModel::new();
2049        let result = model.load_buffer(ERRFILE3, "test3", true);
2050        assert!(result.is_ok());
2051        let result = model.load_buffer(ERRFILE4, "test4", true);
2052        let error = result.unwrap_err();
2053        assert!(matches!(error, AutosarDataError::InvalidFileMerge { .. }));
2054
2055        // non-overlapping files
2056        const FILEBUF3: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2057        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2058        <AR-PACKAGES>
2059          <AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME></AR-PACKAGE>
2060          <AR-PACKAGE><SHORT-NAME>Package2</SHORT-NAME></AR-PACKAGE>
2061        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
2062        const FILEBUF4: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2063        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2064        <AR-PACKAGES>
2065        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
2066        let model_a = AutosarModel::new();
2067        model_a.load_buffer(FILEBUF3, "test5", true).unwrap();
2068        model_a.load_buffer(FILEBUF4, "test6", true).unwrap();
2069        // load the files into model_b in reverse order
2070        let model_b = AutosarModel::new();
2071        model_b.load_buffer(FILEBUF4, "test5", true).unwrap();
2072        model_b.load_buffer(FILEBUF3, "test6", true).unwrap();
2073        // the two models should be equal
2074        model_a.sort();
2075        let model_a_txt = model_a.root_element().serialize();
2076        model_b.sort();
2077        let model_b_txt = model_b.root_element().serialize();
2078        assert_eq!(model_a_txt, model_b_txt);
2079    }
2080
2081    #[test]
2082    fn remove_file() {
2083        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
2084        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2085        <AR-PACKAGES>
2086        <AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME></AR-PACKAGE>
2087        </AR-PACKAGES></AUTOSAR>"#;
2088        const FILEBUF2: &str = r#"<?xml version="1.0" encoding="utf-8"?>
2089        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00049.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2090        <AR-PACKAGES>
2091        <AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
2092        <ELEMENTS><CAN-CLUSTER><SHORT-NAME>CAN_Cluster</SHORT-NAME></CAN-CLUSTER></ELEMENTS>
2093        </AR-PACKAGE>
2094        </AR-PACKAGES></AUTOSAR>"#;
2095        const FILEBUF3: &str = r#"<?xml version="1.0" encoding="utf-8"?>
2096        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00048.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2097        <AR-PACKAGES>
2098        <AR-PACKAGE><SHORT-NAME>Package2</SHORT-NAME>
2099        <ELEMENTS><SYSTEM><SHORT-NAME>System</SHORT-NAME>
2100        <FIBEX-ELEMENTS><FIBEX-ELEMENT-REF-CONDITIONAL>
2101            <FIBEX-ELEMENT-REF DEST="CAN-CLUSTER">/Package/CAN_Cluster</FIBEX-ELEMENT-REF>
2102        </FIBEX-ELEMENT-REF-CONDITIONAL></FIBEX-ELEMENTS>
2103        </SYSTEM></ELEMENTS></AR-PACKAGE>
2104        </AR-PACKAGES></AUTOSAR>"#;
2105        // easy case: remove the only file
2106        let model = AutosarModel::new();
2107        let (file, _) = model.load_buffer(FILEBUF.as_bytes(), "test", true).unwrap();
2108        assert_eq!(model.files().count(), 1);
2109        assert_eq!(model.identifiable_elements().count(), 1);
2110        model.remove_file(&file);
2111        assert_eq!(model.files().count(), 0);
2112        assert_eq!(model.identifiable_elements().count(), 0);
2113        // complicated: remove one of several files
2114        let model = AutosarModel::new();
2115        model.load_buffer(FILEBUF.as_bytes(), "test1", true).unwrap();
2116        assert_eq!(model.files().count(), 1);
2117        let modeltxt_1 = model.root_element().serialize();
2118        let (file2, _) = model.load_buffer(FILEBUF2.as_bytes(), "test2", true).unwrap();
2119        assert_eq!(model.files().count(), 2);
2120        let modeltxt_1_2 = model.root_element().serialize();
2121        assert_ne!(modeltxt_1, modeltxt_1_2);
2122        let (file3, _) = model.load_buffer(FILEBUF3.as_bytes(), "test3", true).unwrap();
2123        assert_eq!(model.files().count(), 3);
2124        let modeltxt_1_2_3 = model.root_element().serialize();
2125        assert_ne!(modeltxt_1_2, modeltxt_1_2_3);
2126        model.get_element_by_path("/Package2/System").unwrap();
2127        model.remove_file(&file3);
2128        // the serialized text of the model after deleting file 3 should be the same as it was before loading file 3
2129        let modeltxt_1_2_x = model.root_element().serialize();
2130        assert_eq!(modeltxt_1_2, modeltxt_1_2_x);
2131        model.remove_file(&file2);
2132        // the serialized text of the model after deleting files 2 and 3 should be the same as it was before loading files 2 and 3
2133        let modeltxt_1_x_x = model.root_element().serialize();
2134        assert_eq!(modeltxt_1, modeltxt_1_x_x);
2135        assert_eq!(model.files().count(), 1);
2136    }
2137
2138    #[test]
2139    fn remove_last_file_clears_reference_caches() {
2140        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2141<AUTOSAR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://autosar.org/schema/r4.0" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd">
2142    <AR-PACKAGES>
2143        <AR-PACKAGE>
2144            <SHORT-NAME>BasePkg</SHORT-NAME>
2145            <ELEMENTS>
2146                <ECU-INSTANCE>
2147                    <SHORT-NAME>Ecu</SHORT-NAME>
2148                </ECU-INSTANCE>
2149            </ELEMENTS>
2150        </AR-PACKAGE>
2151        <AR-PACKAGE>
2152            <SHORT-NAME>RefPkg</SHORT-NAME>
2153            <REFERENCE-BASES>
2154                <REFERENCE-BASE>
2155                    <SHORT-LABEL>BaseA</SHORT-LABEL>
2156                    <PACKAGE-REF DEST="AR-PACKAGE">/BasePkg</PACKAGE-REF>
2157                </REFERENCE-BASE>
2158            </REFERENCE-BASES>
2159            <ELEMENTS>
2160                <SYSTEM>
2161                    <SHORT-NAME>Sys</SHORT-NAME>
2162                    <FIBEX-ELEMENTS>
2163                        <FIBEX-ELEMENT-REF-CONDITIONAL>
2164                            <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="BaseA">Ecu</FIBEX-ELEMENT-REF>
2165                        </FIBEX-ELEMENT-REF-CONDITIONAL>
2166                    </FIBEX-ELEMENTS>
2167                </SYSTEM>
2168            </ELEMENTS>
2169        </AR-PACKAGE>
2170    </AR-PACKAGES>
2171</AUTOSAR>"#
2172        .as_bytes();
2173
2174        let model = AutosarModel::new();
2175        let (file, _) = model.load_buffer(FILEBUF, "test", true).unwrap();
2176
2177        assert!(!model.0.read().relative_references.is_empty());
2178        assert!(!model.0.read().reference_origins.is_empty());
2179        model.remove_file(&file);
2180
2181        assert!(model.0.read().relative_references.is_empty());
2182        assert!(model.0.read().reference_origins.is_empty());
2183    }
2184
2185    #[test]
2186    fn refcount() {
2187        let model = AutosarModel::default();
2188        let weak = model.downgrade();
2189        let project2 = weak.upgrade();
2190        assert_eq!(Arc::strong_count(&model.0), 2);
2191        assert_eq!(model, project2.unwrap());
2192    }
2193
2194    #[test]
2195    fn identifiables_iterator() {
2196        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
2197        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2198        <AR-PACKAGES>
2199        <AR-PACKAGE><SHORT-NAME>OuterPackage1</SHORT-NAME>
2200            <AR-PACKAGES>
2201                <AR-PACKAGE><SHORT-NAME>InnerPackage1</SHORT-NAME></AR-PACKAGE>
2202                <AR-PACKAGE><SHORT-NAME>InnerPackage2</SHORT-NAME></AR-PACKAGE>
2203            </AR-PACKAGES>
2204        </AR-PACKAGE>
2205        <AR-PACKAGE><SHORT-NAME>OuterPackage2</SHORT-NAME>
2206            <AR-PACKAGES>
2207                <AR-PACKAGE><SHORT-NAME>InnerPackage1</SHORT-NAME></AR-PACKAGE>
2208                <AR-PACKAGE><SHORT-NAME>InnerPackage2</SHORT-NAME></AR-PACKAGE>
2209            </AR-PACKAGES>
2210        </AR-PACKAGE>
2211        </AR-PACKAGES></AUTOSAR>"#;
2212        let model = AutosarModel::new();
2213        model.load_buffer(FILEBUF.as_bytes(), "test", true).unwrap();
2214        let mut identifiable_elements = model.identifiable_elements().collect::<Vec<_>>();
2215        identifiable_elements.sort_by(|a, b| a.0.cmp(&b.0));
2216        assert_eq!(identifiable_elements[0].0, "/OuterPackage1");
2217        assert_eq!(identifiable_elements[1].0, "/OuterPackage1/InnerPackage1");
2218        assert_eq!(identifiable_elements[2].0, "/OuterPackage1/InnerPackage2");
2219        assert_eq!(identifiable_elements[3].0, "/OuterPackage2");
2220        assert_eq!(identifiable_elements[4].0, "/OuterPackage2/InnerPackage1");
2221        assert_eq!(identifiable_elements[5].0, "/OuterPackage2/InnerPackage2");
2222    }
2223
2224    #[test]
2225    fn check_references() {
2226        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
2227        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2228        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Pkg</SHORT-NAME>
2229            <ELEMENTS>
2230                <SYSTEM><SHORT-NAME>System</SHORT-NAME>
2231                    <FIBEX-ELEMENTS>
2232                        <FIBEX-ELEMENT-REF-CONDITIONAL>
2233                            <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE">/Pkg/EcuInstance</FIBEX-ELEMENT-REF>
2234                        </FIBEX-ELEMENT-REF-CONDITIONAL>
2235                        <FIBEX-ELEMENT-REF-CONDITIONAL>
2236                            <FIBEX-ELEMENT-REF DEST="I-SIGNAL-I-PDU">/Some/Invalid/Path</FIBEX-ELEMENT-REF>
2237                        </FIBEX-ELEMENT-REF-CONDITIONAL>
2238                        <FIBEX-ELEMENT-REF-CONDITIONAL>
2239                            <FIBEX-ELEMENT-REF DEST="I-SIGNAL">/Pkg/System</FIBEX-ELEMENT-REF>
2240                        </FIBEX-ELEMENT-REF-CONDITIONAL>
2241                    </FIBEX-ELEMENTS>
2242                </SYSTEM>
2243                <ECU-INSTANCE><SHORT-NAME>EcuInstance</SHORT-NAME></ECU-INSTANCE>
2244            </ELEMENTS>
2245        </AR-PACKAGE>
2246        </AR-PACKAGES></AUTOSAR>"#;
2247        let model = AutosarModel::new();
2248        model.load_buffer(FILEBUF.as_bytes(), "test", true).unwrap();
2249        let el_system = model.get_element_by_path("/Pkg/System").unwrap();
2250        let el_fibex_elements = el_system.get_sub_element(ElementName::FibexElements).unwrap();
2251        let el_fibex_element_ref = el_fibex_elements
2252            .create_sub_element(ElementName::FibexElementRefConditional)
2253            .and_then(|ferc| ferc.create_sub_element(ElementName::FibexElementRef))
2254            .unwrap();
2255        el_fibex_element_ref.set_character_data("/Pkg/System").unwrap();
2256        // the test data contains 4 references to 3 distinct items:
2257        // - to /Pkg/EcuInstance (VALID)
2258        // - to /Some/Invalid/Path (INVALID)
2259        // - to /Pkg/System, with DEST=I-SIGNAL (INVALID)
2260        // - to /Pkg/System, without DEST (INVALID)
2261        assert_eq!(model.0.read().reference_origins.len(), 3);
2262
2263        // confirm that the first reference, to EcuInstance, is valid
2264        let el_fbx_ref1 = el_fibex_elements
2265            .get_sub_element_at(0)
2266            .and_then(|ferc| ferc.get_sub_element(ElementName::FibexElementRef))
2267            .unwrap();
2268        assert_eq!(
2269            el_fbx_ref1.get_reference_target().unwrap().element_name(),
2270            ElementName::EcuInstance
2271        );
2272
2273        let invalid_refs = model
2274            .check_references()
2275            .iter()
2276            .filter_map(WeakElement::upgrade)
2277            .collect::<Vec<_>>();
2278        assert_eq!(invalid_refs.len(), 3);
2279        let ref0 = &invalid_refs[0];
2280        assert_eq!(ref0.element_name(), ElementName::FibexElementRef);
2281        let refpath = ref0.character_data().and_then(|cdata| cdata.string_value()).unwrap();
2282        // there is no defined order in which the references will be checked, so any of the three broken refs could be returned first
2283        assert!(refpath == "/Pkg/System" || refpath == "/Some/Invalid/Path");
2284
2285        model.get_element_by_path("/Pkg/EcuInstance").unwrap();
2286        let refs = model.get_references_to("/Pkg/EcuInstance");
2287        assert_eq!(refs.len(), 1);
2288        let refs = model.get_references_to("nonexistent");
2289        assert!(refs.is_empty());
2290        assert_eq!(model.verify_reference_caches(), Ok(()));
2291    }
2292
2293    #[test]
2294    fn serialize_files() {
2295        let model = AutosarModel::default();
2296        let file1 = model.create_file("filename1", AutosarVersion::Autosar_00042).unwrap();
2297        let file2 = model.create_file("filename2", AutosarVersion::Autosar_00042).unwrap();
2298
2299        let result = model.serialize_files();
2300        assert_eq!(result.len(), 2);
2301        assert_eq!(
2302            result.get(&PathBuf::from("filename1")).unwrap(),
2303            &file1.serialize().unwrap()
2304        );
2305        assert_eq!(
2306            result.get(&PathBuf::from("filename2")).unwrap(),
2307            &file2.serialize().unwrap()
2308        );
2309    }
2310
2311    #[test]
2312    fn duplicate() {
2313        let model = AutosarModel::new();
2314        let file1 = model.create_file("filename1", AutosarVersion::Autosar_00042).unwrap();
2315        let file2 = model.create_file("filename2", AutosarVersion::Autosar_00042).unwrap();
2316        let el_ar_packages = model
2317            .root_element()
2318            .create_sub_element(ElementName::ArPackages)
2319            .unwrap();
2320        let el_pkg1 = el_ar_packages
2321            .create_named_sub_element(ElementName::ArPackage, "pkg1")
2322            .unwrap();
2323        let el_pkg2 = el_ar_packages
2324            .create_named_sub_element(ElementName::ArPackage, "pkg2")
2325            .unwrap();
2326
2327        assert_eq!(el_ar_packages.file_membership().unwrap().1.len(), 2);
2328        el_pkg1.remove_from_file(&file2).unwrap();
2329        assert_eq!(el_pkg1.file_membership().unwrap().1.len(), 1);
2330        el_pkg2.remove_from_file(&file1).unwrap();
2331        assert_eq!(el_pkg2.file_membership().unwrap().1.len(), 1);
2332
2333        let model2 = model.duplicate().unwrap();
2334        assert_eq!(model2.files().count(), 2);
2335        let mut files_iter = model2.files();
2336        // get the files out of model 2
2337        let mut model2_file1 = files_iter.next().unwrap();
2338        let mut model2_file2 = files_iter.next().unwrap();
2339        // the iterator could return the files in any order - make sure that model2_file1 corresponds to file1
2340        if model2_file1.filename() != file1.filename() {
2341            std::mem::swap(&mut model2_file1, &mut model2_file2);
2342        }
2343
2344        assert_eq!(file1.filename(), model2_file1.filename());
2345        assert_eq!(file2.filename(), model2_file2.filename());
2346        assert_eq!(file1.serialize().unwrap(), model2_file1.serialize().unwrap());
2347        assert_eq!(file2.serialize().unwrap(), model2_file2.serialize().unwrap());
2348    }
2349
2350    /// duplicate() must copy a model with files of different versions exactly
2351    ///
2352    /// Each file of the copy keeps the version of the original file, so every element is valid
2353    /// where it ends up. Filtering the copy by any single version loses data: the oldest version
2354    /// of the model does not permit the elements which were added in later versions, and the
2355    /// newest version does not permit the elements which were removed again before it.
2356    #[test]
2357    fn duplicate_mixed_versions() {
2358        // PORT-BLUEPRINT exists in AUTOSAR 4.0.1, but was removed in later versions
2359        const FILEBUF_OLD: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2360        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-0-1.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2361        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>PkgOld</SHORT-NAME><ELEMENTS>
2362          <PORT-BLUEPRINT><SHORT-NAME>Blueprint</SHORT-NAME></PORT-BLUEPRINT>
2363        </ELEMENTS></AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
2364        // ADAPTIVE-APPLICATION-SW-COMPONENT-TYPE was only added after 4.0.1
2365        const FILEBUF_NEW: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2366        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2367        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>PkgNew</SHORT-NAME><ELEMENTS>
2368          <ADAPTIVE-APPLICATION-SW-COMPONENT-TYPE><SHORT-NAME>Adaptive</SHORT-NAME></ADAPTIVE-APPLICATION-SW-COMPONENT-TYPE>
2369        </ELEMENTS></AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
2370
2371        let model = AutosarModel::new();
2372        let (file_old, _) = model.load_buffer(FILEBUF_OLD, "old.arxml", true).unwrap();
2373        let (file_new, _) = model.load_buffer(FILEBUF_NEW, "new.arxml", true).unwrap();
2374
2375        let copy = model.duplicate().unwrap();
2376
2377        // an element which only exists in versions older than the newest file must not be dropped
2378        assert!(copy.get_element_by_path("/PkgOld/Blueprint").is_some());
2379        // an element which only exists in versions newer than the oldest file must not be dropped
2380        assert!(copy.get_element_by_path("/PkgNew/Adaptive").is_some());
2381        assert_eq!(model.elements_dfs().count(), copy.elements_dfs().count());
2382
2383        // The file membership must end up on the same elements as in the original. If any element
2384        // were dropped from the copy, then the file membership would be shifted onto the wrong
2385        // elements, and the files would no longer contain the same data.
2386        assert_eq!(copy.files().count(), 2);
2387        let copy_old = copy.files().find(|f| f.filename() == file_old.filename()).unwrap();
2388        let copy_new = copy.files().find(|f| f.filename() == file_new.filename()).unwrap();
2389        assert_eq!(copy_old.version(), AutosarVersion::Autosar_4_0_1);
2390        assert_eq!(copy_new.version(), AutosarVersion::Autosar_00050);
2391        assert_eq!(copy_old.serialize().unwrap(), file_old.serialize().unwrap());
2392        assert_eq!(copy_new.serialize().unwrap(), file_new.serialize().unwrap());
2393    }
2394
2395    #[test]
2396    fn write() {
2397        let model = AutosarModel::default();
2398        // write an empty model, it does nothing since there are no files
2399        model.write().unwrap();
2400
2401        let dir = tempdir().unwrap();
2402        let filename = dir.path().with_file_name("new.arxml");
2403        model.create_file(&filename, AutosarVersion::LATEST).unwrap();
2404        model.write().unwrap();
2405        assert!(filename.exists());
2406
2407        let filename = PathBuf::from("nonexistent/dir/some_file.arxml");
2408        let model = AutosarModel::default();
2409        // creating an ArxmlFile with a non-existent directory is not an error
2410        model.create_file(&filename, AutosarVersion::LATEST).unwrap();
2411        // the write operation will fail, because the directory does not exist
2412        let result = model.write();
2413        assert!(result.is_err());
2414    }
2415
2416    #[test]
2417    fn traits() {
2418        // AutosarModel: Debug, Clone, Hash
2419        let model = AutosarModel::new();
2420        let model_cloned = model.clone();
2421        assert_eq!(model, model_cloned);
2422        assert_eq!(format!("{model:#?}"), format!("{model_cloned:#?}"));
2423        #[allow(clippy::mutable_key_type)]
2424        let mut hashset = HashSet::<AutosarModel>::new();
2425        hashset.insert(model);
2426        let inserted = hashset.insert(model_cloned);
2427        assert!(!inserted);
2428
2429        // CharacterData
2430        let cdata = CharacterData::String("x".to_string());
2431        let cdata2 = cdata.clone();
2432        assert_eq!(cdata, cdata2);
2433        assert_eq!(format!("{cdata:#?}"), format!("{cdata2:#?}"));
2434
2435        // ContentType
2436        let ct: ContentType = ContentType::Elements;
2437        let ct2 = ct;
2438        assert_eq!(ct, ct2);
2439        assert_eq!(format!("{ct:#?}"), format!("{ct2:#?}"));
2440    }
2441
2442    #[test]
2443    fn elements_dfs_with_max_depth() {
2444        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2445        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2446        <AR-PACKAGES>
2447          <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
2448            <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
2449              <SHORT-NAME>BswModuleValues</SHORT-NAME>
2450              <PARAMETER-VALUES>
2451                <ECUC-NUMERICAL-PARAM-VALUE>
2452                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
2453                </ECUC-NUMERICAL-PARAM-VALUE>
2454                <ECUC-NUMERICAL-PARAM-VALUE>
2455                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
2456                </ECUC-NUMERICAL-PARAM-VALUE>
2457                <ECUC-NUMERICAL-PARAM-VALUE>
2458                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_C</DEFINITION-REF>
2459                </ECUC-NUMERICAL-PARAM-VALUE>
2460              </PARAMETER-VALUES>
2461            </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
2462          </ELEMENTS></AR-PACKAGE>
2463          <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
2464          <AR-PACKAGE><SHORT-NAME>Pkg_C</SHORT-NAME></AR-PACKAGE>
2465        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
2466        let model = AutosarModel::new();
2467        let (_, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
2468        let all_count = model.elements_dfs().count();
2469        let lvl2_count = model.elements_dfs_with_max_depth(2).count();
2470        assert!(all_count > lvl2_count);
2471        for elem in model.elements_dfs_with_max_depth(2) {
2472            assert!(elem.0 <= 2);
2473        }
2474    }
2475
2476    #[test]
2477    fn model_merge() {
2478        // from github issue #24; test files provided by FlTr
2479        const FILE_A: &[u8] = br#"<?xml version="1.0" encoding="utf-8"?>
2480<AUTOSAR xmlns="http://autosar.org/schema/r4.0"
2481         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2482         xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00048.xsd">
2483  <AR-PACKAGES>
2484    <AR-PACKAGE>
2485      <SHORT-NAME>EcucModuleConfigurationValuess</SHORT-NAME>
2486      <ELEMENTS>
2487        <ECUC-MODULE-CONFIGURATION-VALUES>
2488          <SHORT-NAME>A</SHORT-NAME>
2489          <DEFINITION-REF DEST="ECUC-MODULE-DEF">/AUTOSAR_A</DEFINITION-REF>
2490          <CONTAINERS>
2491            <ECUC-CONTAINER-VALUE>
2492              <SHORT-NAME>AB</SHORT-NAME>
2493              <DEFINITION-REF DEST="ECUC-PARAM-CONF-CONTAINER-DEF">/AUTOSAR_A/B</DEFINITION-REF>
2494              <PARAMETER-VALUES>
2495                <ECUC-NUMERICAL-PARAM-VALUE>
2496                  <DEFINITION-REF DEST="ECUC-FLOAT-PARAM-DEF">/AUTOSAR_A/B/D</DEFINITION-REF>
2497                  <VALUE>0.01</VALUE>
2498                </ECUC-NUMERICAL-PARAM-VALUE>
2499                <ECUC-TEXTUAL-PARAM-VALUE>
2500                  <DEFINITION-REF DEST="ECUC-ENUMERATION-PARAM-DEF">/AUTOSAR_A/B/E</DEFINITION-REF>
2501                  <VALUE>ABC42</VALUE>
2502                </ECUC-TEXTUAL-PARAM-VALUE>
2503              </PARAMETER-VALUES>
2504            </ECUC-CONTAINER-VALUE>
2505          </CONTAINERS>
2506        </ECUC-MODULE-CONFIGURATION-VALUES>
2507      </ELEMENTS>
2508    </AR-PACKAGE>
2509  </AR-PACKAGES>
2510</AUTOSAR>
2511        "#;
2512
2513        const FILE_B: &[u8] = br#"<?xml version="1.0" encoding="utf-8"?>
2514<AUTOSAR xmlns="http://autosar.org/schema/r4.0"
2515         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2516         xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00048.xsd">
2517  <AR-PACKAGES>
2518    <AR-PACKAGE>
2519      <SHORT-NAME>EcucModuleConfigurationValuess</SHORT-NAME>
2520      <ELEMENTS>
2521        <ECUC-MODULE-CONFIGURATION-VALUES>
2522          <SHORT-NAME>A</SHORT-NAME>
2523          <DEFINITION-REF DEST="ECUC-MODULE-DEF">/AUTOSAR_A</DEFINITION-REF>
2524          <CONTAINERS>
2525            <ECUC-CONTAINER-VALUE>
2526              <SHORT-NAME>AB</SHORT-NAME>
2527              <DEFINITION-REF DEST="ECUC-PARAM-CONF-CONTAINER-DEF">/AUTOSAR_A/B</DEFINITION-REF>
2528              <PARAMETER-VALUES>
2529                <ECUC-NUMERICAL-PARAM-VALUE>
2530                  <DEFINITION-REF DEST="ECUC-INTEGER-PARAM-DEF">/AUTOSAR_A/B/C</DEFINITION-REF>
2531                  <VALUE>0</VALUE>
2532                </ECUC-NUMERICAL-PARAM-VALUE>
2533                <ECUC-NUMERICAL-PARAM-VALUE>
2534                  <DEFINITION-REF DEST="ECUC-FLOAT-PARAM-DEF">/AUTOSAR_A/B/D</DEFINITION-REF>
2535                  <VALUE>0.01</VALUE>
2536                </ECUC-NUMERICAL-PARAM-VALUE>
2537              </PARAMETER-VALUES>
2538            </ECUC-CONTAINER-VALUE>
2539          </CONTAINERS>
2540        </ECUC-MODULE-CONFIGURATION-VALUES>
2541      </ELEMENTS>
2542    </AR-PACKAGE>
2543  </AR-PACKAGES>
2544</AUTOSAR>"#;
2545
2546        // loading these files must not hang, regardless of the order
2547        let model = AutosarModel::new();
2548        let (_, _) = model.load_buffer(FILE_A, "file_a", true).unwrap();
2549        let (_, _) = model.load_buffer(FILE_B, "file_b", true).unwrap();
2550        // sort the model to ensure that the serialized text is the same
2551        model.sort();
2552        let model_txt = model.root_element().serialize();
2553
2554        let model2 = AutosarModel::new();
2555        let (_, _) = model2.load_buffer(FILE_B, "file_b", true).unwrap();
2556        let (_, _) = model2.load_buffer(FILE_A, "file_a", true).unwrap();
2557        // sort the model to ensure that the serialized text is the same
2558        model2.sort();
2559        let model2_txt = model2.root_element().serialize();
2560
2561        assert_eq!(model_txt, model2_txt);
2562    }
2563
2564    #[test]
2565    fn model_merge_2() {
2566        // regression test for github issue #30
2567        const FILEBUF1: &[u8] = br#"<?xml version="1.0" encoding="UTF-8"?>
2568<AUTOSAR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://autosar.org/schema/r4.0" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-3-0.xsd">
2569  <AR-PACKAGES>
2570    <AR-PACKAGE>
2571      <SHORT-NAME>BSWMD_Package</SHORT-NAME>
2572      <ELEMENTS>
2573        <BSW-MODULE-DESCRIPTION>
2574          <SHORT-NAME>BSWMD</SHORT-NAME>
2575          <IMPLEMENTED-ENTRYS>
2576            <BSW-MODULE-ENTRY-REF-CONDITIONAL>
2577              <BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_A0</BSW-MODULE-ENTRY-REF>
2578            </BSW-MODULE-ENTRY-REF-CONDITIONAL>
2579          </IMPLEMENTED-ENTRYS>
2580        </BSW-MODULE-DESCRIPTION>
2581      </ELEMENTS>
2582    </AR-PACKAGE>
2583  </AR-PACKAGES>
2584</AUTOSAR>"#;
2585        const FILEBUF2: &[u8] = br#"<?xml version="1.0" encoding="UTF-8"?>
2586<AUTOSAR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://autosar.org/schema/r4.0" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-3-0.xsd">
2587  <AR-PACKAGES>
2588    <AR-PACKAGE>
2589      <SHORT-NAME>BSWMD_Package</SHORT-NAME>
2590      <ELEMENTS>
2591        <BSW-MODULE-DESCRIPTION>
2592          <SHORT-NAME>BSWMD</SHORT-NAME>
2593          <IMPLEMENTED-ENTRYS>
2594            <BSW-MODULE-ENTRY-REF-CONDITIONAL>
2595              <BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_B0</BSW-MODULE-ENTRY-REF>
2596            </BSW-MODULE-ENTRY-REF-CONDITIONAL>
2597            <BSW-MODULE-ENTRY-REF-CONDITIONAL>
2598              <BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_B1</BSW-MODULE-ENTRY-REF>
2599            </BSW-MODULE-ENTRY-REF-CONDITIONAL>
2600          </IMPLEMENTED-ENTRYS>
2601        </BSW-MODULE-DESCRIPTION>
2602      </ELEMENTS>
2603    </AR-PACKAGE>
2604  </AR-PACKAGES>
2605</AUTOSAR>"#;
2606
2607        let model = AutosarModel::new();
2608        let (_, _) = model.load_buffer(FILEBUF1, "file1", true).unwrap();
2609        let (_, _) = model.load_buffer(FILEBUF2, "file2", true).unwrap();
2610
2611        let a0_refs = model.get_references_to("/path/to/entry_A0");
2612        assert!(a0_refs.len() == 1);
2613        assert!(a0_refs[0].upgrade().is_some());
2614        let b0_refs = model.get_references_to("/path/to/entry_B0");
2615        assert!(b0_refs.len() == 1);
2616        assert!(b0_refs[0].upgrade().is_some());
2617        let b1_refs = model.get_references_to("/path/to/entry_B1");
2618        assert!(b1_refs.len() == 1);
2619        assert!(b1_refs[0].upgrade().is_some());
2620        assert_eq!(model.verify_reference_caches(), Ok(()));
2621    }
2622
2623    #[test]
2624    fn model_merge_3() {
2625        const FILEBUF1: &[u8] = br#"<?xml version="1.0" encoding="utf-8"?>
2626<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2627<AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
2628  <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
2629    <SHORT-NAME>BswModuleValues</SHORT-NAME>
2630    <PARAMETER-VALUES>
2631      <ECUC-NUMERICAL-PARAM-VALUE>
2632        <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
2633        <VALUE>1</VALUE>
2634      </ECUC-NUMERICAL-PARAM-VALUE>
2635    </PARAMETER-VALUES>
2636  </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
2637</ELEMENTS></AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
2638        const FILEBUF2: &[u8] = br#"<?xml version="1.0" encoding="utf-8"?>
2639<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2640<AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
2641  <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
2642    <SHORT-NAME>BswModuleValues</SHORT-NAME>
2643    <PARAMETER-VALUES>
2644      <ECUC-NUMERICAL-PARAM-VALUE>
2645        <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
2646        <ANNOTATIONS/>
2647        <VALUE>2</VALUE>
2648      </ECUC-NUMERICAL-PARAM-VALUE>
2649    </PARAMETER-VALUES>
2650  </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
2651</ELEMENTS></AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#;
2652
2653        // the merge of the two filee has a conflict: the same ECUC-NUMERICAL-PARAM-VALUE is defined in both files, but with different values.
2654        // Since both params have the same defintion ref they must be merged and cannot be imported side-by-side, which is impossible here.
2655        // This means that loading the second file must fail.
2656
2657        let model = AutosarModel::new();
2658        let (_, _) = model.load_buffer(FILEBUF1, "file1", true).unwrap();
2659        let result = model.load_buffer(FILEBUF2, "file2", true);
2660        assert!(result.is_err());
2661    }
2662
2663    #[test]
2664    fn data_merge_after_insertion_conflict() {
2665        // Both files contain a PORT-API-OPTION. PORT-API-OPTION is neither identifiable nor
2666        // does it have a DEFINITION-REF, so the two elements are paired for merging. The merge
2667        // is not possible, because PORT-REF may only appear once and the two files disagree
2668        // about its value. PORT-API-OPTIONS is splittable, so the element from the second file
2669        // is added as an additional sub element instead.
2670        // ENABLE-TAKE-ADDRESS and PORT-ARG-VALUES are unique to the second file and are already
2671        // merged into the element of the first file when the conflict is detected; the recovery
2672        // must undo this, otherwise these elements end up in both PORT-API-OPTIONs at once.
2673        const FILEBUF1: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2674        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2675        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Pkg</SHORT-NAME><ELEMENTS>
2676          <SERVICE-SW-COMPONENT-TYPE><SHORT-NAME>Swc</SHORT-NAME>
2677            <INTERNAL-BEHAVIORS><SWC-INTERNAL-BEHAVIOR><SHORT-NAME>Behavior</SHORT-NAME>
2678              <PORT-API-OPTIONS>
2679                <PORT-API-OPTION>
2680                  <PORT-REF DEST="P-PORT-PROTOTYPE">/Pkg/Swc/PortA</PORT-REF>
2681                </PORT-API-OPTION>
2682              </PORT-API-OPTIONS>
2683            </SWC-INTERNAL-BEHAVIOR></INTERNAL-BEHAVIORS>
2684          </SERVICE-SW-COMPONENT-TYPE>
2685        </ELEMENTS></AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
2686        const FILEBUF2: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2687        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2688        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Pkg</SHORT-NAME><ELEMENTS>
2689          <SERVICE-SW-COMPONENT-TYPE><SHORT-NAME>Swc</SHORT-NAME>
2690            <INTERNAL-BEHAVIORS><SWC-INTERNAL-BEHAVIOR><SHORT-NAME>Behavior</SHORT-NAME>
2691              <PORT-API-OPTIONS>
2692                <PORT-API-OPTION>
2693                  <ENABLE-TAKE-ADDRESS>true</ENABLE-TAKE-ADDRESS>
2694                  <PORT-ARG-VALUES>
2695                    <PORT-DEFINED-ARGUMENT-VALUE>
2696                      <VALUE-TYPE-TREF DEST="IMPLEMENTATION-DATA-TYPE">/Pkg/SomeType</VALUE-TYPE-TREF>
2697                    </PORT-DEFINED-ARGUMENT-VALUE>
2698                  </PORT-ARG-VALUES>
2699                  <PORT-REF DEST="P-PORT-PROTOTYPE">/Pkg/Swc/PortB</PORT-REF>
2700                </PORT-API-OPTION>
2701              </PORT-API-OPTIONS>
2702            </SWC-INTERNAL-BEHAVIOR></INTERNAL-BEHAVIORS>
2703          </SERVICE-SW-COMPONENT-TYPE>
2704        </ELEMENTS></AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
2705
2706        // serialize each file on its own; merging must not change the content of either file
2707        let single_model = AutosarModel::new();
2708        let (single_file1, _) = single_model.load_buffer(FILEBUF1, "file1.arxml", true).unwrap();
2709        let file1_txt = single_file1.serialize().unwrap();
2710        let single_model = AutosarModel::new();
2711        let (single_file2, _) = single_model.load_buffer(FILEBUF2, "file2.arxml", true).unwrap();
2712        let file2_txt = single_file2.serialize().unwrap();
2713
2714        let model = AutosarModel::new();
2715        let (file1, _) = model.load_buffer(FILEBUF1, "file1.arxml", true).unwrap();
2716        let (file2, _) = model.load_buffer(FILEBUF2, "file2.arxml", true).unwrap();
2717
2718        let el_port_api_options = model
2719            .get_element_by_path("/Pkg/Swc/Behavior")
2720            .and_then(|behavior| behavior.get_sub_element(ElementName::PortApiOptions))
2721            .unwrap();
2722        let options: Vec<Element> = el_port_api_options.sub_elements().collect();
2723        // the two options could not be merged, so each of them exists on its own, one per file
2724        assert_eq!(options.len(), 2);
2725        assert_eq!(options[0].file_membership().unwrap().1.len(), 1);
2726        assert_eq!(options[1].file_membership().unwrap().1.len(), 1);
2727
2728        // the sub elements which are unique to file2 were returned to the option of file2 by the
2729        // rollback, so each of them is present exactly once in the model
2730        for element_name in [ElementName::EnableTakeAddress, ElementName::PortArgValues] {
2731            let count = model
2732                .elements_dfs()
2733                .filter(|(_, elem)| elem.element_name() == element_name)
2734                .count();
2735            assert_eq!(count, 1, "{element_name} exists {count} times, expected 1");
2736        }
2737        // each option contains only the sub elements of its own file
2738        let sub_elements_0: Vec<ElementName> = options[0].sub_elements().map(|e| e.element_name()).collect();
2739        assert_eq!(sub_elements_0, vec![ElementName::PortRef]);
2740        let sub_elements_1: Vec<ElementName> = options[1].sub_elements().map(|e| e.element_name()).collect();
2741        assert_eq!(
2742            sub_elements_1,
2743            vec![
2744                ElementName::EnableTakeAddress,
2745                ElementName::PortArgValues,
2746                ElementName::PortRef
2747            ]
2748        );
2749
2750        // the content of both files is unchanged by the merge
2751        assert_eq!(file1.serialize().unwrap(), file1_txt);
2752        assert_eq!(file2.serialize().unwrap(), file2_txt);
2753
2754        // load the files in the opposite order: the content of both files must still be unchanged
2755        let model = AutosarModel::new();
2756        let (file2, _) = model.load_buffer(FILEBUF2, "file2.arxml", true).unwrap();
2757        let (file1, _) = model.load_buffer(FILEBUF1, "file1.arxml", true).unwrap();
2758        assert_eq!(file1.serialize().unwrap(), file1_txt);
2759        assert_eq!(file2.serialize().unwrap(), file2_txt);
2760    }
2761
2762    // a model with three reference bases: two in the outer package "/BasesPkg", and one in
2763    // "/BasesPkg/SubPackage" whose own PACKAGE-REF is relative to the base "BaseA"
2764    const FILEBUF1_COMPLEX_BASES: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2765<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2766  <AR-PACKAGES>
2767    <AR-PACKAGE><SHORT-NAME>BasesPkg</SHORT-NAME>
2768      <REFERENCE-BASES>
2769        <REFERENCE-BASE>
2770          <SHORT-LABEL>BaseA</SHORT-LABEL>
2771          <PACKAGE-REF DEST="AR-PACKAGE">/ContentPkg</PACKAGE-REF>
2772        </REFERENCE-BASE>
2773        <REFERENCE-BASE>
2774          <SHORT-LABEL>BaseC</SHORT-LABEL>
2775          <PACKAGE-REF DEST="AR-PACKAGE">/ContentPkg2</PACKAGE-REF>
2776        </REFERENCE-BASE>
2777      </REFERENCE-BASES>
2778      <AR-PACKAGES>
2779        <AR-PACKAGE><SHORT-NAME>SubPackage</SHORT-NAME>
2780          <REFERENCE-BASES>
2781            <REFERENCE-BASE>
2782              <SHORT-LABEL>BaseB</SHORT-LABEL>
2783              <PACKAGE-REF DEST="AR-PACKAGE" BASE="BaseA">SubPackage</PACKAGE-REF>
2784            </REFERENCE-BASE>
2785          </REFERENCE-BASES>
2786          <ELEMENTS>
2787            <SYSTEM><SHORT-NAME>System</SHORT-NAME>
2788              <FIBEX-ELEMENTS>
2789                <FIBEX-ELEMENT-REF-CONDITIONAL>
2790                  <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="BaseB">Ecu</FIBEX-ELEMENT-REF>
2791                </FIBEX-ELEMENT-REF-CONDITIONAL>
2792                <FIBEX-ELEMENT-REF-CONDITIONAL>
2793                  <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="BaseC">Ecu</FIBEX-ELEMENT-REF>
2794                </FIBEX-ELEMENT-REF-CONDITIONAL>
2795              </FIBEX-ELEMENTS>
2796            </SYSTEM>
2797          </ELEMENTS>
2798        </AR-PACKAGE>
2799      </AR-PACKAGES>
2800    </AR-PACKAGE>
2801    <AR-PACKAGE><SHORT-NAME>ContentPkg</SHORT-NAME>
2802      <AR-PACKAGES>
2803        <AR-PACKAGE><SHORT-NAME>SubPackage</SHORT-NAME>
2804          <ELEMENTS>
2805            <ECU-INSTANCE><SHORT-NAME>Ecu</SHORT-NAME></ECU-INSTANCE>
2806          </ELEMENTS>
2807        </AR-PACKAGE>
2808      </AR-PACKAGES>
2809    </AR-PACKAGE>
2810    <AR-PACKAGE><SHORT-NAME>ContentPkg2</SHORT-NAME>
2811      <ELEMENTS>
2812        <ECU-INSTANCE><SHORT-NAME>Ecu</SHORT-NAME></ECU-INSTANCE>
2813      </ELEMENTS>
2814    </AR-PACKAGE>
2815  </AR-PACKAGES>
2816</AUTOSAR>"#.as_bytes();
2817
2818    /// a way of corrupting the caches: a description, and the corruption itself. The returned
2819    /// elements are kept alive by the caller, so that entries referring to them are not skipped as
2820    /// dead weak references.
2821    type CacheCorruption = (&'static str, fn(&AutosarModel) -> Vec<Element>);
2822
2823    // the PACKAGE-REF of "BaseA" in FILEBUF1_COMPLEX_BASES, which is an absolute reference
2824    fn get_absolute_package_ref(model: &AutosarModel) -> Element {
2825        model
2826            .get_element_by_path("/BasesPkg")
2827            .and_then(|el_package| el_package.get_sub_element(ElementName::ReferenceBases))
2828            .and_then(|el_bases| el_bases.get_sub_element_at(0))
2829            .and_then(|el_base| el_base.get_sub_element(ElementName::PackageRef))
2830            .unwrap()
2831    }
2832
2833    #[test]
2834    fn verify_reference_caches_detects_inconsistency() {
2835        // verify_reference_caches() is asserted by many tests, so it must be able to fail: this test
2836        // corrupts the caches in each of the ways that real bugs have corrupted them, and requires
2837        // every one of them to be reported.
2838        let corruptions: [CacheCorruption; 8] = [
2839            // a relative reference which is missing from the reverse index entirely
2840            ("missing entry", |model| {
2841                model.0.write().relative_references.clear();
2842                vec![]
2843            }),
2844            // an absolute reference which is missing from the index
2845            ("missing absolute entry", |model| {
2846                let el_package_ref = get_absolute_package_ref(model);
2847                let target = el_package_ref.character_data().unwrap().string_value().unwrap();
2848                model.0.write().reference_origins.remove(&target);
2849                vec![el_package_ref]
2850            }),
2851            // an absolute reference which is registered as a relative one
2852            ("absolute reference in the reverse index", |model| {
2853                let el_package_ref = get_absolute_package_ref(model);
2854                model
2855                    .0
2856                    .write()
2857                    .relative_references
2858                    .insert(el_package_ref.downgrade(), None);
2859                vec![el_package_ref]
2860            }),
2861            // an absolute reference registered under a key which is not its character data
2862            ("wrong key", |model| {
2863                let el_package_ref = get_absolute_package_ref(model);
2864                model
2865                    .0
2866                    .write()
2867                    .reference_origins
2868                    .entry("/Wrong".to_string())
2869                    .or_default()
2870                    .push(el_package_ref.downgrade());
2871                vec![el_package_ref]
2872            }),
2873            // an element in the reverse index which is not a reference at all
2874            ("reverse index entry which is not a reference", |model| {
2875                let el_system = model.get_element_by_path("/BasesPkg/SubPackage/System").unwrap();
2876                model.0.write().relative_references.insert(el_system.downgrade(), None);
2877                vec![el_system]
2878            }),
2879            // a relative reference whose reverse index entry names the wrong target path
2880            ("stale target path", |model| {
2881                let el_ref = get_complex_bases_refs(model).0;
2882                model
2883                    .0
2884                    .write()
2885                    .relative_references
2886                    .insert(el_ref.downgrade(), Some("/Stale".to_string()));
2887                vec![el_ref]
2888            }),
2889            // a reference which is in the reverse index, but not in the bucket it names
2890            ("missing bucket entry", |model| {
2891                let el_ref = get_complex_bases_refs(model).0;
2892                let mut model_locked = model.0.write();
2893                let target = model_locked
2894                    .relative_references
2895                    .get(&el_ref.downgrade())
2896                    .cloned()
2897                    .flatten()
2898                    .unwrap();
2899                model_locked.reference_origins.remove(&target);
2900                vec![el_ref]
2901            }),
2902            // an entry for an element which has been removed from the tree
2903            ("removed element", |model| {
2904                let el_ref = get_complex_bases_refs(model).0;
2905                let el_parent = el_ref.parent().unwrap().unwrap();
2906                model
2907                    .0
2908                    .write()
2909                    .reference_origins
2910                    .insert("/detached".to_string(), vec![el_ref.downgrade()]);
2911                el_parent.remove_sub_element(el_ref.clone()).unwrap();
2912                vec![el_ref]
2913            }),
2914        ];
2915
2916        for (description, corrupt) in corruptions {
2917            let model = AutosarModel::new();
2918            model.load_buffer(FILEBUF1_COMPLEX_BASES, "test", true).unwrap();
2919            assert_eq!(model.verify_reference_caches(), Ok(()));
2920            let _keep_alive = corrupt(&model);
2921            assert!(
2922                model.verify_reference_caches().is_err(),
2923                "verify_reference_caches() did not detect: {description}"
2924            );
2925        }
2926    }
2927
2928    #[test]
2929    fn complex_reference_bases() {
2930        let model = AutosarModel::new();
2931        let result = model.load_buffer(FILEBUF1_COMPLEX_BASES, "test", true);
2932        assert!(result.is_ok());
2933
2934        let el_system = model.get_element_by_path("/BasesPkg/SubPackage/System").unwrap();
2935        let el_fibex_elements = el_system.get_sub_element(ElementName::FibexElements).unwrap();
2936        let el_fibex_element_ref = el_fibex_elements
2937            .get_sub_element_at(0)
2938            .and_then(|ferc| ferc.get_sub_element(ElementName::FibexElementRef))
2939            .unwrap();
2940
2941        // check that we can resolve the reference across multiple levels of reference bases
2942        let el_ecu = el_fibex_element_ref.get_reference_target().unwrap();
2943        assert_eq!(el_ecu.element_name(), ElementName::EcuInstance);
2944
2945        // check that the reference origins are correct
2946        let origins = model.get_references_to(&el_ecu.path().unwrap());
2947        assert_eq!(origins.len(), 1);
2948        let origin = origins[0].upgrade().unwrap();
2949        assert_eq!(origin.element_name(), ElementName::FibexElementRef);
2950
2951        let origins2 = model.get_references_to("/ContentPkg2/Ecu");
2952        assert_eq!(origins2.len(), 1);
2953        assert_eq!(model.verify_reference_caches(), Ok(()));
2954    }
2955
2956    // get the two FIBEX-ELEMENT-REFs of FILEBUF1_COMPLEX_BASES: the first one uses the reference
2957    // base "BaseB" (= /ContentPkg/SubPackage), the second one uses "BaseC" (= /ContentPkg2)
2958    fn get_complex_bases_refs(model: &AutosarModel) -> (Element, Element) {
2959        let el_fibex_elements = model
2960            .get_element_by_path("/BasesPkg/SubPackage/System")
2961            .and_then(|el_system| el_system.get_sub_element(ElementName::FibexElements))
2962            .unwrap();
2963        let mut refs = el_fibex_elements
2964            .sub_elements()
2965            .filter_map(|ferc| ferc.get_sub_element(ElementName::FibexElementRef));
2966        (refs.next().unwrap(), refs.next().unwrap())
2967    }
2968
2969    #[test]
2970    fn rename_relative_reference_target() {
2971        let model = AutosarModel::new();
2972        let result = model.load_buffer(FILEBUF1_COMPLEX_BASES, "test", true);
2973        assert!(result.is_ok());
2974        let (el_ref_base_b, el_ref_base_c) = get_complex_bases_refs(&model);
2975
2976        // rename /ContentPkg/SubPackage/Ecu; the FibexElementRef refers to it using the reference base "BaseB" (/ContentPkg/SubPackage)
2977        let el_ecu = model.get_element_by_path("/ContentPkg/SubPackage/Ecu").unwrap();
2978        el_ecu.set_item_name("RenamedEcu").unwrap();
2979        assert_eq!(
2980            el_ref_base_b.character_data().unwrap().string_value().unwrap(),
2981            "RenamedEcu"
2982        );
2983        assert_eq!(el_ref_base_b.get_reference_target().unwrap(), el_ecu);
2984        // the other reference uses a different base and points at a different element: it is unchanged
2985        assert_eq!(el_ref_base_c.character_data().unwrap().string_value().unwrap(), "Ecu");
2986        assert_eq!(
2987            el_ref_base_c.get_reference_target().unwrap(),
2988            model.get_element_by_path("/ContentPkg2/Ecu").unwrap()
2989        );
2990
2991        // the cache must follow the renaming as well: both references are still known, each under
2992        // the (new) relative path which is the character data of the referring element
2993        assert_eq!(model.get_references_to("/ContentPkg/SubPackage/RenamedEcu").len(), 1);
2994        assert_eq!(model.get_references_to("/ContentPkg/SubPackage/Ecu").len(), 0);
2995        assert_eq!(model.get_references_to("/ContentPkg2/Ecu").len(), 1);
2996        assert!(model.check_references().is_empty());
2997        assert_eq!(model.verify_reference_caches(), Ok(()));
2998    }
2999
3000    #[test]
3001    fn rename_relative_reference_target_repeatedly() {
3002        // renaming the target of a relative reference must leave the caches in a state which allows
3003        // the next rename to find the reference again
3004        let model = AutosarModel::new();
3005        let result = model.load_buffer(FILEBUF1_COMPLEX_BASES, "test", true);
3006        assert!(result.is_ok());
3007        let (el_ref_base_b, _) = get_complex_bases_refs(&model);
3008
3009        let el_ecu = model.get_element_by_path("/ContentPkg/SubPackage/Ecu").unwrap();
3010        for new_name in ["Ecu2", "Ecu3", "Ecu4"] {
3011            el_ecu.set_item_name(new_name).unwrap();
3012            assert_eq!(
3013                el_ref_base_b.character_data().unwrap().string_value().unwrap(),
3014                new_name
3015            );
3016            assert_eq!(el_ref_base_b.get_reference_target().unwrap(), el_ecu);
3017            assert_eq!(model.get_references_to(&el_ecu.path().unwrap()).len(), 1);
3018            assert!(model.check_references().is_empty());
3019        }
3020        assert_eq!(model.verify_reference_caches(), Ok(()));
3021    }
3022
3023    // a model with a relative reference whose relative path consists of more than one path
3024    // component: BASE="BaseA" resolves to /ContentPkg, so "SubPackage/Ecu" leads to
3025    // /ContentPkg/SubPackage/Ecu
3026    const FILEBUF_MULTI_COMPONENT_RELATIVE_REF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
3027<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3028  <AR-PACKAGES>
3029    <AR-PACKAGE><SHORT-NAME>BasesPkg</SHORT-NAME>
3030      <REFERENCE-BASES>
3031        <REFERENCE-BASE>
3032          <SHORT-LABEL>BaseA</SHORT-LABEL>
3033          <PACKAGE-REF DEST="AR-PACKAGE">/ContentPkg</PACKAGE-REF>
3034        </REFERENCE-BASE>
3035      </REFERENCE-BASES>
3036      <ELEMENTS>
3037        <SYSTEM><SHORT-NAME>System</SHORT-NAME>
3038          <FIBEX-ELEMENTS>
3039            <FIBEX-ELEMENT-REF-CONDITIONAL>
3040              <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="BaseA">SubPackage/Ecu</FIBEX-ELEMENT-REF>
3041            </FIBEX-ELEMENT-REF-CONDITIONAL>
3042          </FIBEX-ELEMENTS>
3043        </SYSTEM>
3044      </ELEMENTS>
3045    </AR-PACKAGE>
3046    <AR-PACKAGE><SHORT-NAME>ContentPkg</SHORT-NAME>
3047      <AR-PACKAGES>
3048        <AR-PACKAGE><SHORT-NAME>SubPackage</SHORT-NAME>
3049          <ELEMENTS>
3050            <ECU-INSTANCE><SHORT-NAME>Ecu</SHORT-NAME></ECU-INSTANCE>
3051          </ELEMENTS>
3052        </AR-PACKAGE>
3053      </AR-PACKAGES>
3054    </AR-PACKAGE>
3055  </AR-PACKAGES>
3056</AUTOSAR>"#.as_bytes();
3057
3058    // get the FIBEX-ELEMENT-REF of FILEBUF_MULTI_COMPONENT_RELATIVE_REF
3059    fn get_multi_component_relative_ref(model: &AutosarModel) -> Element {
3060        model
3061            .get_element_by_path("/BasesPkg/System")
3062            .and_then(|el_system| el_system.get_sub_element(ElementName::FibexElements))
3063            .and_then(|el_fibex_elements| el_fibex_elements.get_sub_element_at(0))
3064            .and_then(|ferc| ferc.get_sub_element(ElementName::FibexElementRef))
3065            .unwrap()
3066    }
3067
3068    #[test]
3069    fn rename_package_inside_relative_reference_path() {
3070        // a relative path can consist of several path components, so a renamed element can also be
3071        // an ancestor of the reference target
3072        let model = AutosarModel::new();
3073        let result = model.load_buffer(FILEBUF_MULTI_COMPONENT_RELATIVE_REF, "test", true);
3074        assert!(result.is_ok());
3075        let el_ref = get_multi_component_relative_ref(&model);
3076        let el_ecu = el_ref.get_reference_target().unwrap();
3077
3078        // rename the intermediate package, which is part of the relative path
3079        let el_subpackage = model.get_element_by_path("/ContentPkg/SubPackage").unwrap();
3080        el_subpackage.set_item_name("SubPkgX").unwrap();
3081        assert_eq!(el_ref.character_data().unwrap().string_value().unwrap(), "SubPkgX/Ecu");
3082        assert_eq!(el_ref.get_reference_target().unwrap(), el_ecu);
3083        assert_eq!(model.get_references_to("/ContentPkg/SubPkgX/Ecu").len(), 1);
3084        assert!(model.check_references().is_empty());
3085
3086        // renaming the package which the reference base points to does not change the relative path
3087        let el_content_package = model.get_element_by_path("/ContentPkg").unwrap();
3088        el_content_package.set_item_name("ContentPkgX").unwrap();
3089        assert_eq!(el_ref.character_data().unwrap().string_value().unwrap(), "SubPkgX/Ecu");
3090        assert_eq!(el_ref.get_reference_target().unwrap(), el_ecu);
3091        assert_eq!(model.get_references_to("/ContentPkgX/SubPkgX/Ecu").len(), 1);
3092        assert!(model.check_references().is_empty());
3093        assert_eq!(model.verify_reference_caches(), Ok(()));
3094    }
3095
3096    #[test]
3097    fn move_relative_reference_target() {
3098        // moving the target of a relative reference inside the subtree of its reference base changes
3099        // the relative path, and the reference must follow
3100        let model = AutosarModel::new();
3101        let result = model.load_buffer(FILEBUF_MULTI_COMPONENT_RELATIVE_REF, "test", true);
3102        assert!(result.is_ok());
3103        let el_ref = get_multi_component_relative_ref(&model);
3104        let el_ecu = el_ref.get_reference_target().unwrap();
3105
3106        // create /ContentPkg/SubPackage2 and move the reference target there
3107        let el_elements2 = model
3108            .get_element_by_path("/ContentPkg")
3109            .and_then(|el_package| el_package.get_sub_element(ElementName::ArPackages))
3110            .and_then(|el_packages| {
3111                el_packages
3112                    .create_named_sub_element(ElementName::ArPackage, "SubPackage2")
3113                    .ok()
3114            })
3115            .and_then(|el_package2| el_package2.create_sub_element(ElementName::Elements).ok())
3116            .unwrap();
3117        el_elements2.move_element_here(&el_ecu).unwrap();
3118
3119        assert_eq!(el_ecu.path().unwrap(), "/ContentPkg/SubPackage2/Ecu");
3120        assert_eq!(
3121            el_ref.character_data().unwrap().string_value().unwrap(),
3122            "SubPackage2/Ecu"
3123        );
3124        assert_eq!(el_ref.get_reference_target().unwrap(), el_ecu);
3125        assert_eq!(model.get_references_to("/ContentPkg/SubPackage2/Ecu").len(), 1);
3126        assert!(model.check_references().is_empty());
3127        assert_eq!(model.verify_reference_caches(), Ok(()));
3128    }
3129
3130    #[test]
3131    fn move_relative_reference_target_out_of_base() {
3132        // A relative path can only lead to elements inside the subtree of its reference base, so a
3133        // reference cannot follow a target which is moved out of that subtree. The BASE attribute is
3134        // never rewritten, so the reference keeps its text and becomes invalid.
3135        let model = AutosarModel::new();
3136        let result = model.load_buffer(FILEBUF_MULTI_COMPONENT_RELATIVE_REF, "test", true);
3137        assert!(result.is_ok());
3138        let el_ref = get_multi_component_relative_ref(&model);
3139        let el_ecu = el_ref.get_reference_target().unwrap();
3140
3141        // /BasesPkg is outside the subtree of the reference base, which is /ContentPkg
3142        let el_elements = model
3143            .get_element_by_path("/BasesPkg")
3144            .and_then(|el_package| el_package.get_sub_element(ElementName::Elements))
3145            .unwrap();
3146        el_elements.move_element_here(&el_ecu).unwrap();
3147
3148        assert_eq!(el_ecu.path().unwrap(), "/BasesPkg/Ecu");
3149        // the reference could not follow, so it still contains its original path
3150        assert_eq!(
3151            el_ref.character_data().unwrap().string_value().unwrap(),
3152            "SubPackage/Ecu"
3153        );
3154        assert!(el_ref.get_reference_target().is_err());
3155        assert!(model.get_references_to("/BasesPkg/Ecu").is_empty());
3156        // ... and the now dangling reference is reported
3157        let broken = model.check_references();
3158        assert_eq!(broken.len(), 1);
3159        assert_eq!(broken[0].upgrade().unwrap(), el_ref);
3160        assert_eq!(model.verify_reference_caches(), Ok(()));
3161    }
3162
3163    #[test]
3164    fn rename_package_referenced_by_relative_reference_base() {
3165        // /ContentPkg/SubPackage is named by the PACKAGE-REF of the reference base "BaseB", which is
3166        // itself relative (BASE="BaseA"). Renaming the package must update the character data of that
3167        // PACKAGE-REF, otherwise every reference using "BaseB" breaks.
3168        let model = AutosarModel::new();
3169        let result = model.load_buffer(FILEBUF1_COMPLEX_BASES, "test", true);
3170        assert!(result.is_ok());
3171        let (el_ref_base_b, el_ref_base_c) = get_complex_bases_refs(&model);
3172        let el_ecu = el_ref_base_b.get_reference_target().unwrap();
3173
3174        let el_subpackage = model.get_element_by_path("/ContentPkg/SubPackage").unwrap();
3175        el_subpackage.set_item_name("SubPkgX").unwrap();
3176
3177        let el_package_ref = model
3178            .get_element_by_path("/BasesPkg/SubPackage")
3179            .and_then(|el_package| el_package.get_sub_element(ElementName::ReferenceBases))
3180            .and_then(|el_bases| el_bases.get_sub_element_at(0))
3181            .and_then(|el_base| el_base.get_sub_element(ElementName::PackageRef))
3182            .unwrap();
3183        assert_eq!(
3184            el_package_ref.character_data().unwrap().string_value().unwrap(),
3185            "SubPkgX"
3186        );
3187        // the chained reference base now resolves to the renamed package
3188        assert_eq!(
3189            el_package_ref.resolve_reference_base("BaseB").as_deref(),
3190            Some("/ContentPkg/SubPkgX")
3191        );
3192
3193        // the relative path of the reference is unchanged, but it now resolves through the new base path
3194        assert_eq!(el_ref_base_b.character_data().unwrap().string_value().unwrap(), "Ecu");
3195        assert_eq!(el_ref_base_b.get_reference_target().unwrap(), el_ecu);
3196        assert_eq!(el_ecu.path().unwrap(), "/ContentPkg/SubPkgX/Ecu");
3197        assert_eq!(
3198            el_ref_base_c.get_reference_target().unwrap().path().unwrap(),
3199            "/ContentPkg2/Ecu"
3200        );
3201        assert_eq!(model.get_references_to("/ContentPkg/SubPkgX/Ecu").len(), 1);
3202        assert_eq!(model.get_references_to("/ContentPkg/SubPkgX").len(), 1);
3203        assert!(model.check_references().is_empty());
3204        assert_eq!(model.verify_reference_caches(), Ok(()));
3205    }
3206
3207    #[test]
3208    fn reference_base_scope_respects_path_boundaries() {
3209        // A reference base is in scope for the package which declares it and for all packages
3210        // nested inside it. "/Pkg10" is not nested inside "/Pkg1", even though "/Pkg10" starts with
3211        // the string "/Pkg1", so the base declared by "/Pkg1" must not be usable from "/Pkg10".
3212        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
3213<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3214  <AR-PACKAGES>
3215    <AR-PACKAGE><SHORT-NAME>Pkg1</SHORT-NAME>
3216      <REFERENCE-BASES>
3217        <REFERENCE-BASE>
3218          <SHORT-LABEL>Base</SHORT-LABEL>
3219          <PACKAGE-REF DEST="AR-PACKAGE">/Target</PACKAGE-REF>
3220        </REFERENCE-BASE>
3221      </REFERENCE-BASES>
3222      <ELEMENTS>
3223        <SYSTEM><SHORT-NAME>System</SHORT-NAME>
3224          <FIBEX-ELEMENTS>
3225            <FIBEX-ELEMENT-REF-CONDITIONAL>
3226              <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="Base">Ecu</FIBEX-ELEMENT-REF>
3227            </FIBEX-ELEMENT-REF-CONDITIONAL>
3228          </FIBEX-ELEMENTS>
3229        </SYSTEM>
3230      </ELEMENTS>
3231      <AR-PACKAGES>
3232        <AR-PACKAGE><SHORT-NAME>Sub</SHORT-NAME>
3233          <ELEMENTS>
3234            <SYSTEM><SHORT-NAME>System</SHORT-NAME>
3235              <FIBEX-ELEMENTS>
3236                <FIBEX-ELEMENT-REF-CONDITIONAL>
3237                  <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="Base">Ecu</FIBEX-ELEMENT-REF>
3238                </FIBEX-ELEMENT-REF-CONDITIONAL>
3239              </FIBEX-ELEMENTS>
3240            </SYSTEM>
3241          </ELEMENTS>
3242        </AR-PACKAGE>
3243      </AR-PACKAGES>
3244    </AR-PACKAGE>
3245    <AR-PACKAGE><SHORT-NAME>Pkg10</SHORT-NAME>
3246      <ELEMENTS>
3247        <SYSTEM><SHORT-NAME>System</SHORT-NAME>
3248          <FIBEX-ELEMENTS>
3249            <FIBEX-ELEMENT-REF-CONDITIONAL>
3250              <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="Base">Ecu</FIBEX-ELEMENT-REF>
3251            </FIBEX-ELEMENT-REF-CONDITIONAL>
3252          </FIBEX-ELEMENTS>
3253        </SYSTEM>
3254      </ELEMENTS>
3255    </AR-PACKAGE>
3256    <AR-PACKAGE><SHORT-NAME>Target</SHORT-NAME>
3257      <ELEMENTS>
3258        <ECU-INSTANCE><SHORT-NAME>Ecu</SHORT-NAME></ECU-INSTANCE>
3259      </ELEMENTS>
3260    </AR-PACKAGE>
3261  </AR-PACKAGES>
3262</AUTOSAR>"#.as_bytes();
3263        let model = AutosarModel::new();
3264        model.load_buffer(FILEBUF, "test", true).unwrap();
3265
3266        let get_reference = |system_path: &str| {
3267            model
3268                .get_element_by_path(system_path)
3269                .and_then(|e| e.get_sub_element(ElementName::FibexElements))
3270                .and_then(|e| e.get_sub_element_at(0))
3271                .and_then(|e| e.get_sub_element(ElementName::FibexElementRef))
3272                .unwrap()
3273        };
3274
3275        // the declaring package itself: in scope
3276        let el_declaring = get_reference("/Pkg1/System");
3277        assert_eq!(el_declaring.resolve_reference_base("Base").as_deref(), Some("/Target"));
3278        assert_eq!(
3279            el_declaring.get_reference_target().unwrap().path().unwrap(),
3280            "/Target/Ecu"
3281        );
3282        // nested inside the declaring package: in scope
3283        let el_nested = get_reference("/Pkg1/Sub/System");
3284        assert_eq!(el_nested.resolve_reference_base("Base").as_deref(), Some("/Target"));
3285        assert_eq!(el_nested.get_reference_target().unwrap().path().unwrap(), "/Target/Ecu");
3286        // a sibling package whose name merely starts with the same characters: not in scope, so the
3287        // relative reference in /Pkg10 cannot be resolved
3288        let el_sibling = get_reference("/Pkg10/System");
3289        assert_eq!(el_sibling.resolve_reference_base("Base"), None);
3290        assert!(el_sibling.get_reference_target().is_err());
3291
3292        assert_eq!(model.check_references().len(), 1);
3293        assert_eq!(model.get_references_to("/Target/Ecu").len(), 2);
3294        assert_eq!(model.verify_reference_caches(), Ok(()));
3295    }
3296
3297    #[test]
3298    fn cyclic_reference_base_terminates() {
3299        // A REFERENCE-BASE may use another REFERENCE-BASE as its own base. Invalid data can make
3300        // that chain cyclic; resolving such a base must fail instead of looping forever.
3301        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
3302<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3303  <AR-PACKAGES>
3304    <AR-PACKAGE><SHORT-NAME>Pkg</SHORT-NAME>
3305      <REFERENCE-BASES>
3306        <REFERENCE-BASE>
3307          <SHORT-LABEL>SelfRef</SHORT-LABEL>
3308          <PACKAGE-REF DEST="AR-PACKAGE" BASE="SelfRef">Sub</PACKAGE-REF>
3309        </REFERENCE-BASE>
3310        <REFERENCE-BASE>
3311          <SHORT-LABEL>MutualA</SHORT-LABEL>
3312          <PACKAGE-REF DEST="AR-PACKAGE" BASE="MutualB">SubA</PACKAGE-REF>
3313        </REFERENCE-BASE>
3314        <REFERENCE-BASE>
3315          <SHORT-LABEL>MutualB</SHORT-LABEL>
3316          <PACKAGE-REF DEST="AR-PACKAGE" BASE="MutualA">SubB</PACKAGE-REF>
3317        </REFERENCE-BASE>
3318      </REFERENCE-BASES>
3319      <ELEMENTS>
3320        <SYSTEM><SHORT-NAME>System</SHORT-NAME>
3321          <FIBEX-ELEMENTS>
3322            <FIBEX-ELEMENT-REF-CONDITIONAL>
3323              <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="SelfRef">Ecu</FIBEX-ELEMENT-REF>
3324            </FIBEX-ELEMENT-REF-CONDITIONAL>
3325            <FIBEX-ELEMENT-REF-CONDITIONAL>
3326              <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="MutualA">Ecu</FIBEX-ELEMENT-REF>
3327            </FIBEX-ELEMENT-REF-CONDITIONAL>
3328          </FIBEX-ELEMENTS>
3329        </SYSTEM>
3330      </ELEMENTS>
3331    </AR-PACKAGE>
3332  </AR-PACKAGES>
3333</AUTOSAR>"#.as_bytes();
3334        let model = AutosarModel::new();
3335        model.load_buffer(FILEBUF, "test", true).unwrap();
3336
3337        let el_fibex_elements = model
3338            .get_element_by_path("/Pkg/System")
3339            .and_then(|e| e.get_sub_element(ElementName::FibexElements))
3340            .unwrap();
3341        let mut references = el_fibex_elements
3342            .sub_elements()
3343            .filter_map(|ferc| ferc.get_sub_element(ElementName::FibexElementRef));
3344        // a reference base which is its own base
3345        let el_self_cycle = references.next().unwrap();
3346        assert_eq!(el_self_cycle.resolve_reference_base("SelfRef"), None);
3347        assert!(el_self_cycle.get_reference_target().is_err());
3348        // two reference bases which are each other's base
3349        let el_mutual_cycle = references.next().unwrap();
3350        assert_eq!(el_mutual_cycle.resolve_reference_base("MutualA"), None);
3351        assert!(el_mutual_cycle.get_reference_target().is_err());
3352
3353        // the two references above, plus the three PACKAGE-REFs, which are themselves relative
3354        // references that cannot be resolved because of the cycles they are part of
3355        assert_eq!(model.check_references().len(), 5);
3356        assert_eq!(model.verify_reference_caches(), Ok(()));
3357    }
3358
3359    #[test]
3360    fn load_duplicate_reference_bases() {
3361        // An AR-PACKAGE may be split across several files, each of them repeating the same
3362        // REFERENCE-BASE. The merge keeps only one REFERENCE-BASE element, so the cache must not
3363        // contain a duplicate entry either - otherwise removing the element would leave a stale
3364        // entry behind which still resolves references.
3365        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
3366<AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00050.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
3367  <AR-PACKAGES>
3368    <AR-PACKAGE><SHORT-NAME>Pkg</SHORT-NAME>
3369      <REFERENCE-BASES>
3370        <REFERENCE-BASE>
3371          <SHORT-LABEL>Base</SHORT-LABEL>
3372          <PACKAGE-REF DEST="AR-PACKAGE">/Target</PACKAGE-REF>
3373        </REFERENCE-BASE>
3374      </REFERENCE-BASES>
3375    </AR-PACKAGE>
3376  </AR-PACKAGES>
3377</AUTOSAR>"#.as_bytes();
3378        let model = AutosarModel::new();
3379        model.load_buffer(FILEBUF, "file1", true).unwrap();
3380        model.load_buffer(FILEBUF, "file2", true).unwrap();
3381
3382        // the merge keeps a single REFERENCE-BASE element, and the reference resolves through it
3383        let el_reference_bases = model
3384            .get_element_by_path("/Pkg")
3385            .and_then(|e| e.get_sub_element(ElementName::ReferenceBases))
3386            .unwrap();
3387        assert_eq!(el_reference_bases.sub_elements().count(), 1);
3388        let el_package_ref = el_reference_bases
3389            .get_sub_element_at(0)
3390            .and_then(|e| e.get_sub_element(ElementName::PackageRef))
3391            .unwrap();
3392        assert_eq!(
3393            el_package_ref.resolve_reference_base("Base").as_deref(),
3394            Some("/Target")
3395        );
3396
3397        // removing that element removes the declaration for good
3398        let el_package = model.get_element_by_path("/Pkg").unwrap();
3399        el_package.remove_sub_element(el_reference_bases).unwrap();
3400        assert_eq!(el_package_ref.resolve_reference_base("Base"), None);
3401        assert_eq!(model.verify_reference_caches(), Ok(()));
3402    }
3403
3404    #[test]
3405    fn duplicate_model_resolves_relative_references() {
3406        // AutosarModel::duplicate() copies the element tree into a new model, so the relative references
3407        // in the copy must be resolved against the reference bases of the copy
3408        let model = AutosarModel::new();
3409        model.load_buffer(FILEBUF1_COMPLEX_BASES, "test", true).unwrap();
3410        let copy = model.duplicate().unwrap();
3411
3412        let el_fibex_element_ref = copy
3413            .get_element_by_path("/BasesPkg/SubPackage/System")
3414            .and_then(|e| e.get_sub_element(ElementName::FibexElements))
3415            .and_then(|e| e.get_sub_element_at(0))
3416            .and_then(|e| e.get_sub_element(ElementName::FibexElementRef))
3417            .unwrap();
3418        assert_eq!(
3419            el_fibex_element_ref.get_reference_target().unwrap().path().unwrap(),
3420            "/ContentPkg/SubPackage/Ecu"
3421        );
3422        assert!(copy.check_references().is_empty());
3423        assert_eq!(model.verify_reference_caches(), Ok(()));
3424    }
3425}