Skip to main content

autosar_data/
autosarmodel.rs

1use std::{
2    collections::{HashMap, VecDeque},
3    hash::Hash,
4};
5
6use crate::*;
7
8#[derive(Debug)]
9// actions to be taken when merging two elements
10enum MergeAction {
11    MergeEqual,
12    MergeUnequal(Element),
13    AOnly,
14    BOnly(usize),
15}
16
17impl AutosarModel {
18    /// Create an `AutosarData` model
19    ///
20    /// Initially it contains no arxml files and only has a default `<AUTOSAR>` element
21    ///
22    /// # Example
23    ///
24    /// ```
25    /// # use autosar_data::*;
26    /// let model = AutosarModel::new();
27    /// ```
28    ///
29    #[must_use]
30    pub fn new() -> AutosarModel {
31        let version = AutosarVersion::LATEST;
32        let xsi_schemalocation =
33            CharacterData::String(format!("http://autosar.org/schema/r4.0 {}", version.filename()));
34        let xmlns = CharacterData::String("http://autosar.org/schema/r4.0".to_string());
35        let xmlns_xsi = CharacterData::String("http://www.w3.org/2001/XMLSchema-instance".to_string());
36        let root_attributes = smallvec::smallvec![
37            Attribute {
38                attrname: AttributeName::xsiSchemalocation,
39                content: xsi_schemalocation
40            },
41            Attribute {
42                attrname: AttributeName::xmlns,
43                content: xmlns
44            },
45            Attribute {
46                attrname: AttributeName::xmlnsXsi,
47                content: xmlns_xsi
48            },
49        ];
50        let root_elem = ElementRaw {
51            parent: ElementOrModel::None,
52            elemname: ElementName::Autosar,
53            elemtype: ElementType::ROOT,
54            content: SmallVec::new(),
55            attributes: root_attributes,
56            file_membership: HashSet::with_capacity(0),
57            comment: None,
58        }
59        .wrap();
60        let model = AutosarModelRaw {
61            files: Vec::new(),
62            identifiables: FxIndexMap::default(),
63            reference_origins: FxHashMap::default(),
64            relative_reference_origins: FxHashMap::default(),
65            reference_bases: FxHashMap::default(),
66            root_element: root_elem.clone(),
67        }
68        .wrap();
69        root_elem.set_parent(ElementOrModel::Model(model.downgrade()));
70        model
71    }
72
73    /// Create a new [`ArxmlFile`] inside this `AutosarData` structure
74    ///
75    /// You must provide a filename for the [`ArxmlFile`], even if you do not plan to write the data to disk.
76    /// You must also specify an [`AutosarVersion`]. All methods manipulation the data insdie the file will ensure conformity with the version specified here.
77    /// The newly created `ArxmlFile` will be created with a root AUTOSAR element.
78    ///
79    /// # Parameters
80    ///
81    ///  - `filename`: A filename for the data from the buffer. It must be unique within the model.
82    ///    It will be used by `write()`, and is also used to identify this data in error messages.
83    ///  - `version`: The [`AutosarVersion`] that will be used by the data created inside this file
84    ///
85    /// # Example
86    ///
87    /// ```
88    /// # use autosar_data::*;
89    /// # fn main() -> Result<(), AutosarDataError> {
90    /// let model = AutosarModel::new();
91    /// let file = model.create_file("filename.arxml", AutosarVersion::Autosar_00050)?;
92    /// # Ok(())
93    /// # }
94    /// ```
95    ///
96    /// # Errors
97    ///
98    ///  - [`AutosarDataError::DuplicateFilenameError`]: The model already contains a file with this filename
99    ///
100    pub fn create_file<P: AsRef<Path>>(
101        &self,
102        filename: P,
103        version: AutosarVersion,
104    ) -> Result<ArxmlFile, AutosarDataError> {
105        let mut data = self.0.write();
106
107        if data.files.iter().any(|af| af.filename() == filename.as_ref()) {
108            return Err(AutosarDataError::DuplicateFilenameError {
109                verb: "create",
110                filename: filename.as_ref().to_path_buf(),
111            });
112        }
113
114        let new_file = ArxmlFile::new(filename, version, self);
115
116        data.files.push(new_file.clone());
117
118        // every file contains the root element (but not its children)
119        let _ = data.root_element.add_to_file_restricted(&new_file);
120
121        Ok(new_file)
122    }
123
124    /// Load a named buffer containing arxml data
125    ///
126    /// 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.
127    ///
128    /// # Parameters:
129    ///
130    ///  - `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.
131    ///  - `filename`: the original filename of the data, or a newly generated name that is unique within the `AutosarData` instance.
132    ///  - `strict`: toggle strict parsing. Some parsing errors are recoverable and can be issued as warnings.
133    ///
134    /// This method may be called concurrently on multiple threads to load different buffers
135    ///
136    /// # Example
137    ///
138    /// ```no_run
139    /// # use autosar_data::*;
140    /// # fn main() -> Result<(), AutosarDataError> {
141    /// let model = AutosarModel::new();
142    /// # let buffer = b"";
143    /// model.load_buffer(buffer, "filename.arxml", true)?;
144    /// # Ok(())
145    /// # }
146    /// ```
147    ///
148    /// # Errors
149    ///
150    ///  - [`AutosarDataError::DuplicateFilenameError`]: The model already contains a file with this filename
151    ///  - [`AutosarDataError::OverlappingDataError`]: The new data contains Autosar paths that are already defined by the existing data
152    ///  - [`AutosarDataError::ParserError`]: The parser detected an error; the source field gives further details
153    ///
154    pub fn load_buffer<P: AsRef<Path>>(
155        &self,
156        buffer: &[u8],
157        filename: P,
158        strict: bool,
159    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarDataError> {
160        self.load_buffer_internal(buffer, filename.as_ref().to_path_buf(), strict)
161    }
162
163    fn load_buffer_internal(
164        &self,
165        buffer: &[u8],
166        filename: PathBuf,
167        strict: bool,
168    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarDataError> {
169        if self.files().any(|file| file.filename() == filename) {
170            return Err(AutosarDataError::DuplicateFilenameError { verb: "load", filename });
171        }
172
173        let mut parser = ArxmlParser::new(filename.clone(), buffer, strict);
174        let root_element = parser.parse_arxml()?;
175        let version = parser.get_fileversion();
176        let arxml_file = ArxmlFileRaw {
177            version,
178            model: self.downgrade(),
179            filename: filename.clone(),
180            xml_standalone: parser.get_standalone(),
181        }
182        .wrap();
183
184        if self.0.read().files.is_empty() {
185            root_element.set_parent(ElementOrModel::Model(self.downgrade()));
186            root_element.0.write().file_membership.insert(arxml_file.downgrade());
187            self.0.write().root_element = root_element;
188        } else {
189            let result = self.merge_file_data(&root_element, arxml_file.downgrade());
190            if let Err(error) = result {
191                let _ = self.root_element().remove_from_file(&arxml_file);
192                return Err(error);
193            }
194        }
195
196        let mut data = self.0.write();
197        // import identifiables from the parser, check for conflicts with existing data
198        data.identifiables.reserve(parser.identifiables.len());
199        for (key, value) in parser.identifiables {
200            // the same identifiables can be present in multiple files
201            // in this case we only keep the first one
202            if let Some(existing_element) = data.identifiables.get(&key).and_then(WeakElement::upgrade) {
203                // present in both
204                if let Some(new_element) = value.upgrade()
205                    && existing_element.element_name() != new_element.element_name()
206                {
207                    // referenced element is different on both sides
208                    return Err(AutosarDataError::OverlappingDataError {
209                        filename,
210                        path: new_element.xml_path(),
211                    });
212                }
213            } else {
214                data.identifiables.insert(key, value);
215            }
216        }
217
218        // import references from the parser
219        data.reference_origins.reserve(parser.references.len());
220        for (refpath, referring_element, base) in parser.references {
221            if let Some(base_label) = base {
222                //relative reference
223                if let Some(xref) = data.relative_reference_origins.get_mut(&refpath) {
224                    xref.push((referring_element, base_label));
225                } else {
226                    data.relative_reference_origins
227                        .insert(refpath, vec![(referring_element, base_label)]);
228                }
229            } else {
230                // absolute reference
231                if let Some(xref) = data.reference_origins.get_mut(&refpath) {
232                    xref.push(referring_element);
233                } else {
234                    data.reference_origins.insert(refpath, vec![referring_element]);
235                }
236            }
237        }
238
239        // import reference bases from the parser
240        data.reference_bases.reserve(parser.reference_bases.len());
241        for (base_key, base_info) in parser.reference_bases {
242            if let Some(existing_base) = data.reference_bases.get_mut(&base_key) {
243                existing_base.push(base_info);
244            } else {
245                data.reference_bases.insert(base_key, vec![base_info]);
246            }
247        }
248
249        data.files.push(arxml_file.clone());
250
251        Ok((arxml_file, parser.warnings))
252    }
253
254    // Merge the elements from an incoming arxml file into the overall model
255    //
256    // The Autosar standard specifies that the data can be split across multiple arxml files
257    // It states that each ARXML file can represent an "AUTOSAR Partial Model".
258    // The possible partitioning is marked in the meta model, where some elements have the attribute "splitable".
259    // These are the points where the overall elements can be split into different arxml files, or, while loading, merged.
260    // Unfortunately, the standard says nothing about how this should be done, so the algorithm here is just a guess.
261    // In the wild, only merging at the AR-PACKAGES and at the ELEMENTS level exists. Everything else seems like a bad idea anyway.
262    fn merge_file_data(&self, new_root: &Element, new_file: WeakArxmlFile) -> Result<(), AutosarDataError> {
263        let root = self.root_element();
264        let files: HashSet<WeakArxmlFile> = self.files().map(|f| f.downgrade()).collect();
265
266        Self::merge_element(&root, &files, new_root, &new_file).map_err(|e| {
267            // transform ElementInsertionConflict into InvalidFileMerge
268            if let AutosarDataError::ElementInsertionConflict { parent_path, .. } = &e {
269                AutosarDataError::InvalidFileMerge {
270                    path: parent_path.clone(),
271                }
272            } else {
273                e
274            }
275        })?;
276
277        self.root_element().0.write().file_membership.insert(new_file);
278
279        Ok(())
280    }
281
282    fn merge_element(
283        parent_a: &Element,
284        files: &HashSet<WeakArxmlFile>,
285        parent_b: &Element,
286        new_file: &WeakArxmlFile,
287    ) -> Result<(), AutosarDataError> {
288        let mut iter_a = parent_a.sub_elements().enumerate();
289        let mut iter_b = parent_b.sub_elements();
290        let mut item_a = iter_a.next();
291        let mut item_b = iter_b.next();
292        let mut elements_a_only = Vec::<Element>::new();
293        let mut elements_b_only = Vec::<(Element, usize)>::new();
294        let mut elements_merge = Vec::<(Element, Element)>::new();
295        let min_ver_a = files
296            .iter()
297            .filter_map(|weak| weak.upgrade().map(|f| f.version()))
298            .min()
299            .unwrap_or(AutosarVersion::LATEST);
300        let min_ver_b = new_file.upgrade().map_or(AutosarVersion::LATEST, |f| f.version());
301        let version = std::cmp::min(min_ver_a, min_ver_b);
302        let splitable = parent_a.element_type().splittable_in(version);
303
304        while let (Some((pos_a, elem_a)), Some(elem_b)) = (&item_a, &item_b) {
305            let merge_action = if elem_a.element_name() == elem_b.element_name() {
306                if elem_a.is_identifiable() {
307                    Self::calc_identifiables_merge(parent_a, parent_b, elem_a, elem_b, splitable)?
308                } else {
309                    Self::calc_element_merge(parent_b, elem_a, elem_b)
310                }
311            } else {
312                // a and b are different kinds of elements. This is only allowed if parent is splittable
313                let parent_type = parent_a.element_type();
314                // The following check does not work, real examples still fail:
315                // if !parent_type.splittable_in(self.version()) && parent_a.element_name() != ElementName::ArPackage {
316                //     return Err(AutosarDataError::InvalidFileMerge { path: parent_a.xml_path() });
317                // }
318
319                let (_, indices_a) = parent_type.find_sub_element(elem_a.element_name(), u32::MAX).unwrap();
320                let (_, indices_b) = parent_type.find_sub_element(elem_b.element_name(), u32::MAX).unwrap();
321                if indices_a < indices_b {
322                    // elem_a comes before elem_b, advance only a
323                    // a: <parent> | <a = child 1> <child 2>
324                    // b: <parent> |               <b = child 2>
325                    MergeAction::AOnly
326                } else {
327                    // elem_b comes before elem_a, advance only b
328                    // a: <parent> |               <a = child 2>
329                    // b: <parent> | <b = child 1> <child 2>
330                    MergeAction::BOnly(*pos_a)
331                }
332            };
333
334            match merge_action {
335                MergeAction::MergeEqual => {
336                    elements_merge.push((elem_a.clone(), elem_b.clone()));
337                    item_a = iter_a.next();
338                    item_b = iter_b.next();
339                }
340                MergeAction::MergeUnequal(other_b) => {
341                    elements_merge.push((elem_a.clone(), other_b));
342                    item_a = iter_a.next();
343                }
344                MergeAction::AOnly => {
345                    elements_a_only.push(elem_a.clone());
346                    item_a = iter_a.next();
347                }
348                MergeAction::BOnly(position) => {
349                    if !elements_merge.iter().any(|(_, merge_b)| merge_b == elem_b) {
350                        elements_b_only.push((elem_b.clone(), position));
351                    }
352                    item_b = iter_b.next();
353                }
354            }
355        }
356        // at least one of the two iterators has reached the end
357        // make sure the other one also reaches the end
358        if let Some((_, elem_a)) = item_a {
359            elements_a_only.push(elem_a);
360            for (_, elem_a) in iter_a {
361                elements_a_only.push(elem_a);
362            }
363        }
364        if let Some(elem_b) = item_b {
365            let elem_count = parent_a.0.read().content.len();
366            if !elements_merge.iter().any(|(_, merge_b)| merge_b == &elem_b) {
367                elements_b_only.push((elem_b, elem_count));
368            }
369            for elem_b in iter_b {
370                if !elements_merge.iter().any(|(_, merge_b)| merge_b == &elem_b) {
371                    elements_b_only.push((elem_b, elem_count));
372                }
373            }
374        }
375
376        // elements in elements_a_only are already present in the model, so they only need to be restricted
377        for element in elements_a_only {
378            // files contains the permisions of the parent
379            let mut elem_locked = element.0.write();
380            if elem_locked.file_membership.is_empty() {
381                files.clone_into(&mut elem_locked.file_membership);
382            }
383        }
384
385        // elements in elements_b_only are not present in the model yet, so they need to be added
386        // this step can fail, in which case the merge of this element fails
387        Self::import_new_items(parent_a, elements_b_only, new_file, min_ver_b)?;
388
389        // recurse for sub elements that are present on both sides: these need to be checked and merged
390        Self::merge_sub_elements(parent_a, elements_merge, files, new_file, version)?;
391
392        Ok(())
393    }
394
395    // calculate how to merge two identifiable elements
396    // precondition: both elements have the same element_name
397    fn calc_identifiables_merge(
398        parent_a: &Element,
399        parent_b: &Element,
400        elem_a: &Element,
401        elem_b: &Element,
402        splitable: bool,
403    ) -> Result<MergeAction, AutosarDataError> {
404        Ok(if elem_a.item_name() == elem_b.item_name() {
405            // equal
406            // advance both iterators
407            MergeAction::MergeEqual
408        } else {
409            // assume that the ordering on both sides is different
410            // find a match for a among the siblings of b
411            if let Some(sibling) = parent_b
412                .sub_elements()
413                .find(|e| e.element_name() == elem_a.element_name() && e.item_name() == elem_a.item_name())
414            {
415                // matching item found
416                MergeAction::MergeUnequal(sibling)
417            } else {
418                // element is unique in a
419                if splitable {
420                    MergeAction::AOnly
421                } else {
422                    return Err(AutosarDataError::InvalidFileMerge {
423                        path: parent_a.xml_path(),
424                    });
425                }
426            }
427        })
428    }
429
430    // calculate how to merge two elements which are not identifiable
431    // precondition: both elements have the same element_name
432    fn calc_element_merge(parent_b: &Element, elem_a: &Element, elem_b: &Element) -> MergeAction {
433        // special case for BSW parameters - many elements used here don't have a SHORT-NAME, but they do have a DEFINITION-REF
434        let defref_a = elem_a
435            .get_sub_element(ElementName::DefinitionRef)
436            .and_then(|dr| dr.character_data())
437            .and_then(|cdata| cdata.string_value());
438        let defref_b = elem_b
439            .get_sub_element(ElementName::DefinitionRef)
440            .and_then(|dr| dr.character_data())
441            .and_then(|cdata| cdata.string_value());
442        // defref_a and _b are simply None for all other elements which don't have a definition-ref
443
444        if defref_a == defref_b {
445            // either: defrefs exist and are identical, OR they are both None.
446            if elem_a.character_data() != elem_b.character_data() {
447                // they have different character data, so they are not identical
448                // take only a, defer b
449                MergeAction::AOnly
450            } else {
451                // if they are None, then there is nothing else that can be compared, so we just assume the elements are identical.
452                // Merge them and advance both iterators.
453                MergeAction::MergeEqual
454            }
455        } else {
456            // check if a sibling of elem_b has the same definiton-ref as elem_a
457            // this handles the case where the the elements on both sides are ordered differently
458            if let Some(sibling) = parent_b
459                .sub_elements()
460                .filter(|e| e.element_name() == elem_a.element_name())
461                .find(|e| {
462                    e.get_sub_element(ElementName::DefinitionRef)
463                        .and_then(|dr| dr.character_data())
464                        .and_then(|cdata| cdata.string_value())
465                        == defref_a
466                })
467            {
468                // a match for item_a exists
469                MergeAction::MergeUnequal(sibling)
470            } else {
471                // element is unique in A
472                // This case only happens for BSW definition elements, and it appears that these always have a splittable parent
473                MergeAction::AOnly
474            }
475        }
476    }
477
478    fn import_new_items(
479        parent_a: &Element,
480        elements_b_only: Vec<(Element, usize)>,
481        new_file: &WeakArxmlFile,
482        version: AutosarVersion,
483    ) -> Result<(), AutosarDataError> {
484        // elements in elements_b_only are not present in the model yet, so they need to be added
485        for (idx, (new_element, insert_pos)) in elements_b_only.into_iter().enumerate() {
486            // idx number of elements have already been inserted, so the destination position must be adjusted
487            let dest = insert_pos + idx;
488
489            Self::import_single_item(parent_a, new_element, dest, new_file, version)?;
490        }
491        Ok(())
492    }
493
494    fn import_single_item(
495        parent_a: &Element,
496        new_element: Element,
497        dest: usize,
498        new_file: &WeakArxmlFile,
499        version: AutosarVersion,
500    ) -> Result<(), AutosarDataError> {
501        let mut parent_a_locked = parent_a.0.write();
502        let weak_parent_a = parent_a.downgrade();
503
504        new_element.set_parent(ElementOrModel::Element(weak_parent_a));
505        // restrict new_element, it is only present in new_file
506        new_element.0.write().file_membership.insert(new_file.clone());
507
508        // add the new_element (from side b) to the content of parent_a
509        // to do this, first check valid element insertion positions
510        let (first_pos, last_pos) = parent_a_locked.calc_element_insert_range(new_element.element_name(), version)?;
511
512        // clamp dest, so that first_pos <= dest <= last_pos
513        let dest = dest.max(first_pos).min(last_pos);
514
515        // insert the element from b at the calculated position
516        parent_a_locked
517            .content
518            .insert(dest, ElementContent::Element(new_element));
519
520        Ok(())
521    }
522
523    fn merge_sub_elements(
524        parent_a: &Element,
525        elements_merge: Vec<(Element, Element)>,
526        files: &HashSet<WeakArxmlFile>,
527        new_file: &WeakArxmlFile,
528        version: AutosarVersion,
529    ) -> Result<(), AutosarDataError> {
530        for (elem_a, elem_b) in elements_merge {
531            // get the list of files that the element from a is present in
532            let files = if !elem_a.0.read().file_membership.is_empty() {
533                elem_a.0.read().file_membership.clone()
534            } else {
535                files.clone()
536            };
537
538            // merge the two elements
539            let result = AutosarModel::merge_element(&elem_a, &files, &elem_b, new_file);
540            match result {
541                Ok(()) => {
542                    // update the file membership of the merged element, if there was any
543                    let mut elem_a_locked = elem_a.0.write();
544                    if !elem_a_locked.file_membership.is_empty() {
545                        elem_a_locked.file_membership.insert(new_file.clone());
546                    }
547                }
548                Err(e) => {
549                    if let AutosarDataError::ElementInsertionConflict { parent_path, .. } = &e {
550                        // failed to merge sub element due to insertion conflict
551                        if elem_a.is_identifiable() {
552                            // if the merge of an identifiable element fails, then the whole merge fails
553                            return Err(AutosarDataError::InvalidFileMerge {
554                                path: parent_path.clone(),
555                            });
556                        } else if parent_a.element_type().splittable_in(version) {
557                            elem_a.set_file_membership(files);
558                            // try to import elem_b as a new item instead
559                            let dest = elem_a.position().unwrap_or_default() + 1;
560                            return Self::import_single_item(parent_a, elem_b, dest, new_file, version).map_err(|_| e);
561                        }
562                    }
563
564                    // propagate the error back to the parent, perhaps it can be handled there
565                    return Err(e);
566                }
567            }
568        }
569        Ok(())
570    }
571
572    /// Load an arxml file
573    ///
574    /// This function is a wrapper around `load_buffer` to make the common case of loading a file from disk more convenient
575    ///
576    /// # Parameters:
577    ///
578    ///  - `filename`: the original filename of the data, or a newly generated name that is unique within the `AutosarData` instance.
579    ///  - `strict`: toggle strict parsing. Some parsing errors are recoverable and can be issued as warnings.
580    ///
581    /// # Example
582    ///
583    /// ```no_run
584    /// # use autosar_data::*;
585    /// # fn main() -> Result<(), AutosarDataError> {
586    /// let model = AutosarModel::new();
587    /// model.load_file("filename.arxml", true)?;
588    /// # Ok(())
589    /// # }
590    /// ```
591    ///
592    /// # Errors
593    ///
594    ///  - [`AutosarDataError::IoErrorRead`]: There was an error while reading the file
595    ///  - [`AutosarDataError::DuplicateFilenameError`]: The model already contains a file with this filename
596    ///  - [`AutosarDataError::OverlappingDataError`]: The new data contains Autosar paths that are already defined by the existing data
597    ///  - [`AutosarDataError::ParserError`]: The parser detected an error; the source field gives further details
598    ///
599    pub fn load_file<P: AsRef<Path>>(
600        &self,
601        filename: P,
602        strict: bool,
603    ) -> Result<(ArxmlFile, Vec<AutosarDataError>), AutosarDataError> {
604        let filename_buf = filename.as_ref().to_path_buf();
605        let buffer = std::fs::read(&filename_buf).map_err(|err| AutosarDataError::IoErrorRead {
606            filename: filename_buf.clone(),
607            ioerror: err,
608        })?;
609
610        self.load_buffer(&buffer, &filename_buf, strict)
611    }
612
613    /// remove a file from the model
614    ///
615    /// # Parameters:
616    ///
617    ///  - `file`: The file that will be removed from the model
618    ///
619    /// # Example
620    ///
621    /// ```
622    /// # use autosar_data::*;
623    /// # fn main() -> Result<(), AutosarDataError> {
624    /// let model = AutosarModel::new();
625    /// let file = model.create_file("filename.arxml", AutosarVersion::Autosar_00050)?;
626    /// model.remove_file(&file);
627    /// # Ok(())
628    /// # }
629    /// ```
630    pub fn remove_file(&self, file: &ArxmlFile) {
631        let mut locked_model = self.0.write();
632        let find_result = locked_model
633            .files
634            .iter()
635            .enumerate()
636            .find(|(_, f)| *f == file)
637            .map(|(pos, _)| pos);
638        // find_result is stored first so that the lock on model is dropped
639        if let Some(pos) = find_result {
640            locked_model.files.swap_remove(pos);
641            if locked_model.files.is_empty() {
642                // no other files remain in the model, so it reverts to being empty
643                locked_model.root_element.0.write().content.clear();
644                locked_model.root_element.set_file_membership(HashSet::new());
645                locked_model.identifiables.clear();
646                locked_model.reference_origins.clear();
647                locked_model.relative_reference_origins.clear();
648                locked_model.reference_bases.clear();
649            } else {
650                drop(locked_model);
651                // other files still contribute elements, so only the elements specifically associated with this file should be removed
652                let _ = self.root_element().remove_from_file(file);
653            }
654        }
655    }
656
657    /// serialize each of the files in the model
658    ///
659    /// returns the result in a `HashMap` of <`file_name`, `file_content`>
660    ///
661    /// # Example
662    ///
663    /// ```
664    /// # use autosar_data::*;
665    /// # fn main() -> Result<(), AutosarDataError> {
666    /// let model = AutosarModel::new();
667    /// for (pathbuf, file_content) in model.serialize_files() {
668    ///     // do something with it
669    /// }
670    /// # Ok(())
671    /// # }
672    /// ```
673    ///
674    #[must_use]
675    pub fn serialize_files(&self) -> HashMap<PathBuf, String> {
676        let mut result = HashMap::new();
677        for file in self.files() {
678            if let Ok(data) = file.serialize() {
679                result.insert(file.filename(), data);
680            }
681        }
682        result
683    }
684
685    /// write all files in the model
686    ///
687    /// This is a wrapper around `serialize_files`. The current filename of each file will be used to write the serialized data.
688    ///
689    /// If any of the individual files cannot be written, then `write()` will abort and return the error.
690    /// This may result in a situation where some files have been written and others have not.
691    ///
692    /// # Example
693    ///
694    /// ```
695    /// # use autosar_data::*;
696    /// # fn main() -> Result<(), AutosarDataError> {
697    /// let model = AutosarModel::new();
698    /// // load or create files
699    /// model.write()?;
700    /// # Ok(())
701    /// # }
702    /// ```
703    ///
704    /// # Errors
705    ///
706    ///  - [`AutosarDataError::IoErrorWrite`]: There was an error while writing a file
707    pub fn write(&self) -> Result<(), AutosarDataError> {
708        for (pathbuf, filedata) in self.serialize_files() {
709            std::fs::write(pathbuf.clone(), filedata).map_err(|err| AutosarDataError::IoErrorWrite {
710                filename: pathbuf,
711                ioerror: err,
712            })?;
713        }
714        Ok(())
715    }
716
717    /// create an iterator over all [`ArxmlFile`]s in this `AutosarData` object
718    ///
719    /// # Example
720    ///
721    /// ```
722    /// # use autosar_data::*;
723    /// # fn main() -> Result<(), AutosarDataError> {
724    /// let model = AutosarModel::new();
725    /// // load or create files
726    /// for file in model.files() {
727    ///     // do something with the file
728    /// }
729    /// # Ok(())
730    /// # }
731    /// ```
732    #[must_use]
733    pub fn files(&self) -> ArxmlFileIterator {
734        ArxmlFileIterator::new(self.clone())
735    }
736
737    /// Get a reference to the root ```<AUTOSAR ...>``` element of this model
738    ///
739    /// # Example
740    ///
741    /// ```
742    /// # use autosar_data::*;
743    /// # let model = AutosarModel::new();
744    /// # let _file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
745    /// let autosar_element = model.root_element();
746    /// ```
747    #[must_use]
748    pub fn root_element(&self) -> Element {
749        let locked_model = self.0.read();
750        locked_model.root_element.clone()
751    }
752
753    /// get a named element by its Autosar path
754    ///
755    /// This is a lookup in a hash table and runs in O(1) time
756    ///
757    /// # Parameters
758    ///
759    ///  - `path`: The Autosar path to look up
760    ///
761    /// # Example
762    ///
763    /// ```
764    /// # use autosar_data::*;
765    /// # fn main() -> Result<(), AutosarDataError> {
766    /// let model = AutosarModel::new();
767    /// // [...]
768    /// if let Some(element) = model.get_element_by_path("/Path/To/Element") {
769    ///     // use the element
770    /// }
771    /// # Ok(())
772    /// # }
773    /// ```
774    #[must_use]
775    pub fn get_element_by_path(&self, path: &str) -> Option<Element> {
776        let model = self.0.read();
777        model.identifiables.get(path).and_then(WeakElement::upgrade)
778    }
779
780    /// Duplicate the model
781    ///
782    /// This creates a second, fully independent model.
783    /// The original model and the duplicate are not linked in any way and can be modified independently.
784    ///
785    /// # Example
786    /// ```
787    /// # use autosar_data::*;
788    /// # fn main() -> Result<(), AutosarDataError> {
789    /// let model = AutosarModel::new();
790    /// // [...]
791    /// let model_copy = model.duplicate()?;
792    /// assert!(model != model_copy);
793    /// # Ok(())
794    /// # }
795    /// ```
796    ///
797    /// # Errors
798    ///
799    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
800    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
801    ///    The operation was aborted to avoid a deadlock, but can be retried.
802    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
803    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
804    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
805    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
806    pub fn duplicate(&self) -> Result<AutosarModel, AutosarDataError> {
807        let copy = Self::new();
808        let mut filemap = HashMap::new();
809
810        for orig_file in self.files() {
811            let filename = orig_file.filename();
812            let new_file = copy.create_file(filename.clone(), orig_file.version())?;
813            new_file.0.write().xml_standalone = orig_file.0.read().xml_standalone;
814            filemap.insert(filename, new_file.downgrade());
815        }
816
817        // by inserting copies of the sub elements of <AUTOSAR>, we automatically
818        // get up-to-date identifiables and reference_origins
819        for element in self.root_element().sub_elements() {
820            copy.root_element().create_copied_sub_element(&element)?;
821        }
822
823        // `create_copied_sub_element` does not transfer information about file membership
824        // this needs to be added back
825        let orig_iter = self.elements_dfs();
826        let copy_iter = copy.elements_dfs();
827        let combined = std::iter::zip(orig_iter, copy_iter);
828        for ((_, orig_elem), (_, copy_elem)) in combined {
829            let mut locked_copy = copy_elem.0.try_write().ok_or(AutosarDataError::ParentElementLocked)?;
830            locked_copy.file_membership.clear();
831
832            for orig_file in orig_elem.0.read().file_membership.iter().filter_map(|w| w.upgrade()) {
833                if let Some(copy_file) = filemap.get(&orig_file.filename()) {
834                    locked_copy.file_membership.insert(copy_file.clone());
835                }
836            }
837        }
838
839        Ok(copy)
840    }
841
842    /// create a depth-first iterator over all [Element]s in the model
843    ///
844    /// The iterator returns all elements from the merged model, consisting of
845    /// data from all arxml files loaded in this model.
846    ///
847    /// Directly printing the return values could show something like this:
848    ///
849    /// <pre>
850    /// 0: AUTOSAR
851    /// 1: AR-PACKAGES
852    /// 2: AR-PACKAGE
853    /// ...
854    /// 2: AR-PACKAGE
855    /// </pre>
856    ///
857    /// # Example
858    ///
859    /// ```
860    /// # use autosar_data::*;
861    /// # fn main() -> Result<(), AutosarDataError> {
862    /// # let model = AutosarModel::new();
863    /// for (depth, element) in model.elements_dfs() {
864    ///     // [...]
865    /// }
866    /// # Ok(())
867    /// # }
868    /// ```
869    #[must_use]
870    pub fn elements_dfs(&self) -> ElementsDfsIterator {
871        self.root_element().elements_dfs()
872    }
873
874    /// Create a depth first iterator over all [Element]s in this model, up to a maximum depth
875    ///
876    /// The iterator returns all elements from the merged model, consisting of
877    /// data from all arxml files loaded in this model.
878    ///
879    /// # Example
880    ///
881    /// ```
882    /// # use autosar_data::*;
883    /// # let model = AutosarModel::new();
884    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
885    /// # let element = model.root_element();
886    /// # element.create_sub_element(ElementName::ArPackages).unwrap();
887    /// # let sub_elem = element.get_sub_element(ElementName::ArPackages).unwrap();
888    /// # sub_elem.create_named_sub_element(ElementName::ArPackage, "test2").unwrap();
889    /// for (depth, elem) in model.elements_dfs_with_max_depth(1) {
890    ///     assert!(depth <= 1);
891    ///     // ...
892    /// }
893    /// ```
894    #[must_use]
895    pub fn elements_dfs_with_max_depth(&self, max_depth: usize) -> ElementsDfsIterator {
896        self.root_element().elements_dfs_with_max_depth(max_depth)
897    }
898
899    /// Recursively sort all elements in the model. This is exactly identical to calling `sort()` on the root element of the model.
900    ///
901    /// All sub elements of the root element are sorted alphabetically.
902    /// If the sub-elements are named, then the sorting is performed according to the item names,
903    /// otherwise the serialized form of the sub-elements is used for sorting.
904    ///
905    /// Element attributes are not taken into account while sorting.
906    /// The elements are sorted in place, and sorting cannot fail, so there is no return value.
907    ///
908    /// # Example
909    /// ```
910    /// # use autosar_data::*;
911    /// # let model = AutosarModel::new();
912    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
913    /// model.sort();
914    /// ```
915    pub fn sort(&self) {
916        self.root_element().sort();
917    }
918
919    /// Create an iterator over the list of the Autosar paths of all identifiable elements
920    ///
921    /// The list contains the full Autosar path of each element. It is not sorted.
922    ///
923    /// # Example
924    ///
925    /// ```
926    /// # use autosar_data::*;
927    /// # fn main() -> Result<(), AutosarDataError> {
928    /// # let model = AutosarModel::new();
929    /// for (path, _) in model.identifiable_elements() {
930    ///     let element = model.get_element_by_path(&path).unwrap();
931    ///     // [...]
932    /// }
933    /// # Ok(())
934    /// # }
935    /// ```
936    #[must_use]
937    pub fn identifiable_elements(&self) -> IdentifiablesIterator {
938        IdentifiablesIterator::new(self)
939    }
940
941    /// return all elements referring to the given target path
942    ///
943    /// It returns [`WeakElement`]s which must be upgraded to get usable [Element]s.
944    ///
945    /// This is effectively the reverse operation of `get_element_by_path()`
946    ///
947    /// # Parameters
948    ///
949    ///  - `target_path`: The path whose references should be returned
950    ///
951    /// # Example
952    ///
953    /// ```
954    /// # use autosar_data::*;
955    /// # fn main() -> Result<(), AutosarDataError> {
956    /// # let model = AutosarModel::new();
957    /// for weak_element in model.get_references_to("/Path/To/Element") {
958    ///     // [...]
959    /// }
960    /// # Ok(())
961    /// # }
962    /// ```
963    #[must_use]
964    pub fn get_references_to(&self, target_path: &str) -> Vec<WeakElement> {
965        let locked_model = self.0.read();
966        let mut origins = if let Some(origins) = locked_model.reference_origins.get(target_path) {
967            origins.clone()
968        } else {
969            Vec::new()
970        };
971
972        if !locked_model.relative_reference_origins.is_empty() {
973            // also check for relative references
974            let mut pos = target_path.rfind('/').map(|i| i + 1).unwrap_or(0);
975            while pos > 0 {
976                let suffix = &target_path[pos..];
977                if let Some(rel_origins) = locked_model.relative_reference_origins.get(suffix) {
978                    for (origin, base_label) in rel_origins {
979                        let Some(origin_elem) = origin.upgrade() else {
980                            continue;
981                        };
982                        let Some(package_path) = origin_elem.package().ok().flatten().and_then(|p| p.path().ok())
983                        else {
984                            continue;
985                        };
986                        let Some(base_path) = self.resolve_reference_base(base_label, &package_path) else {
987                            continue;
988                        };
989                        if let Some(rest) = target_path.strip_prefix(&base_path) {
990                            let rest = rest.strip_prefix('/').unwrap_or(rest);
991                            if rest == suffix {
992                                origins.push(origin.clone());
993                            }
994                        }
995                    }
996                }
997                pos = target_path[..pos - 1].rfind('/').map(|i| i + 1).unwrap_or(0);
998            }
999        }
1000
1001        origins
1002    }
1003
1004    /// check all Autosar path references and return a list of elements with invalid references
1005    ///
1006    /// For each reference: The target must exist and the DEST attribute must correctly specify the type of the target
1007    ///
1008    /// If no references are invalid, then the return value is an empty list
1009    ///
1010    /// # Example
1011    /// ```
1012    /// # use autosar_data::*;
1013    /// # fn main() -> Result<(), AutosarDataError> {
1014    /// # let model = AutosarModel::new();
1015    /// for broken_ref_weak in model.check_references() {
1016    ///     if let Some(broken_ref) = broken_ref_weak.upgrade() {
1017    ///         // update or delete ref?
1018    ///     }
1019    /// }
1020    /// # Ok(())
1021    /// # }
1022    /// ```
1023    #[must_use]
1024    pub fn check_references(&self) -> Vec<WeakElement> {
1025        let mut broken_refs = Vec::new();
1026
1027        let model = self.0.read();
1028        for (path, element_list) in &model.reference_origins {
1029            if let Some(target_elem_weak) = model.identifiables.get(path) {
1030                // reference target exists
1031                if let Some(target_elem) = target_elem_weak.upgrade() {
1032                    // the target of the reference exists, but the reference can still be technically invalid
1033                    // if the content of the DEST attribute on the reference is wrong
1034                    for referring_elem_weak in element_list {
1035                        if let Some(referring_elem) = referring_elem_weak.upgrade() {
1036                            if let Some(CharacterData::Enum(dest_value)) =
1037                                referring_elem.attribute_value(AttributeName::Dest)
1038                            {
1039                                if !target_elem.element_type().verify_reference_dest(dest_value) {
1040                                    // wrong reference type in the DEST attribute
1041                                    broken_refs.push(referring_elem_weak.clone());
1042                                }
1043                            } else {
1044                                // DEST attribute does not exist - can only happen if broken data was loaded with strict == false
1045                                broken_refs.push(referring_elem_weak.clone());
1046                            }
1047                        }
1048                    }
1049                } else {
1050                    // This case should never happen, possibly panic?
1051                    // The strong ref count of target_elem can only go to zero if the element is removed,
1052                    // but remove_element() should also update data.identifiables and data.reference_origins.
1053                    broken_refs.extend(element_list.iter().cloned());
1054                }
1055            } else {
1056                // reference target does not exist
1057                broken_refs.extend(element_list.iter().cloned());
1058            }
1059        }
1060
1061        broken_refs
1062    }
1063
1064    /// create a weak reference to this data
1065    pub(crate) fn downgrade(&self) -> WeakAutosarModel {
1066        WeakAutosarModel(Arc::downgrade(&self.0))
1067    }
1068
1069    // add an identifiable element to the cache
1070    pub(crate) fn add_identifiable(&self, new_path: String, elem: WeakElement) {
1071        let mut model = self.0.write();
1072        model.identifiables.insert(new_path, elem);
1073    }
1074
1075    // fix a single identifiable element or tree of elements in the cache which has been moved/renamed
1076    pub(crate) fn fix_identifiables(&self, old_path: &str, new_path: &str) {
1077        let mut model = self.0.write();
1078
1079        // the renamed element might contain other identifiable elements that are affected by the renaming
1080        let keys: Vec<String> = model.identifiables.keys().cloned().collect();
1081        for key in keys {
1082            // find keys referring to entries inside the renamed package
1083            if let Some(suffix) = key.strip_prefix(old_path)
1084                && (suffix.is_empty() || suffix.starts_with('/'))
1085            {
1086                let new_key = format!("{new_path}{suffix}");
1087                // fix the identifiables hashmap
1088                if let Some(entry) = model.identifiables.swap_remove(&key) {
1089                    model.identifiables.insert(new_key, entry);
1090                }
1091            }
1092        }
1093    }
1094
1095    // remove a deleted element from the cache
1096    pub(crate) fn remove_identifiable(&self, path: &str) {
1097        let mut model = self.0.write();
1098        model.identifiables.swap_remove(path);
1099    }
1100
1101    pub(crate) fn add_reference_origin(&self, new_ref: &str, base: Option<&str>, origin: WeakElement) {
1102        if let Some(base) = base {
1103            self.add_relative_reference_origin(new_ref, base, origin);
1104        } else {
1105            self.add_absolute_reference_origin(new_ref, origin);
1106        }
1107    }
1108
1109    fn add_absolute_reference_origin(&self, new_ref: &str, origin: WeakElement) {
1110        let mut data = self.0.write();
1111        // add the new entry
1112        if let Some(referrer_list) = data.reference_origins.get_mut(new_ref) {
1113            referrer_list.push(origin);
1114        } else {
1115            data.reference_origins.insert(new_ref.to_owned(), vec![origin]);
1116        }
1117    }
1118
1119    fn add_relative_reference_origin(&self, new_ref: &str, base: &str, origin: WeakElement) {
1120        let mut data = self.0.write();
1121        if let Some(referrer_list) = data.relative_reference_origins.get_mut(new_ref) {
1122            referrer_list.push((origin, base.to_string()));
1123        } else {
1124            data.relative_reference_origins
1125                .insert(new_ref.to_owned(), vec![(origin, base.to_string())]);
1126        }
1127    }
1128
1129    pub(crate) fn fix_reference_origins(
1130        &self,
1131        old_ref: &str,
1132        new_ref: &str,
1133        old_base: Option<&str>,
1134        new_base: Option<&str>,
1135        origin: WeakElement,
1136    ) {
1137        if old_ref != new_ref || old_base != new_base {
1138            self.remove_reference_origin(old_ref, old_base, origin.clone());
1139            self.add_reference_origin(new_ref, new_base, origin);
1140        }
1141    }
1142
1143    pub(crate) fn remove_reference_origin(&self, reference: &str, base: Option<&str>, element: WeakElement) {
1144        if let Some(base) = base {
1145            self.remove_relative_reference_origin(reference, base, element);
1146        } else {
1147            self.remove_absolute_reference_origin(reference, element);
1148        }
1149    }
1150
1151    fn remove_absolute_reference_origin(&self, reference: &str, element: WeakElement) {
1152        let mut data = self.0.write();
1153        let mut count = 1;
1154        if let Some(referrer_list) = data.reference_origins.get_mut(reference) {
1155            if let Some(index) = referrer_list.iter().position(|x| *x == element) {
1156                referrer_list.swap_remove(index);
1157            }
1158            count = referrer_list.len();
1159        }
1160        if count == 0 {
1161            data.reference_origins.remove(reference);
1162        }
1163    }
1164
1165    fn remove_relative_reference_origin(&self, reference: &str, base: &str, element: WeakElement) {
1166        let mut data = self.0.write();
1167        let mut count = 1;
1168        if let Some(referrer_list) = data.relative_reference_origins.get_mut(reference) {
1169            if let Some(index) = referrer_list.iter().position(|(x, b)| *x == element && *b == base) {
1170                referrer_list.swap_remove(index);
1171            }
1172            count = referrer_list.len();
1173        }
1174        if count == 0 {
1175            data.relative_reference_origins.remove(reference);
1176        }
1177    }
1178
1179    pub(crate) fn resolve_reference_base(&self, base: &str, ref_package_path: &str) -> Option<String> {
1180        let model = self.0.read();
1181        let mut current_base = base;
1182        let mut path_components = VecDeque::with_capacity(0);
1183        let mut ref_package_path = ref_package_path;
1184        loop {
1185            // get (potentially) a list of reference bases that all have the requested base label
1186            let ref_base_info_list = model.reference_bases.get(current_base)?;
1187            // select the most applicable reference base from the list based on the owner_package_path (longest prefix match)
1188            let ref_base_info = ref_base_info_list
1189                .iter()
1190                .filter(|info| ref_package_path.starts_with(&info.owner_package_path))
1191                .max_by_key(|info| info.owner_package_path.len())?;
1192
1193            // base is relative to a package, so the path components of the package need to be added to the path
1194            if let Some(package_ref_base) = &ref_base_info.package_ref_base {
1195                // the reference base uses (at least) another reference base as its own base, so we
1196                // loop and build up the path components until we reach a reference base that is absolute
1197                path_components.push_front(&ref_base_info.package_ref);
1198                current_base = package_ref_base;
1199                ref_package_path = &ref_base_info.owner_package_path;
1200            } else {
1201                if path_components.is_empty() {
1202                    return Some(ref_base_info.package_ref.clone());
1203                } else {
1204                    path_components.push_front(&ref_base_info.package_ref);
1205                    // let resolved_path = path_components.join("/"); - join() doesn't exist for &String, so we need to do it manually
1206                    let mut resolved_path = String::new();
1207                    for component in path_components {
1208                        resolved_path.push_str(component);
1209                        resolved_path.push('/');
1210                    }
1211                    resolved_path.pop(); // remove the trailing '/'
1212                    return Some(resolved_path);
1213                }
1214            }
1215        }
1216    }
1217
1218    pub(crate) fn fix_reference_base(
1219        &self,
1220        old_label: Option<String>,
1221        new_label: String,
1222        package_ref_val: String,
1223        ref_base_attr: Option<String>,
1224        owner_package_path: String,
1225    ) {
1226        let mut data = self.0.write();
1227        // Remove the previous entry for this owner package under the old label.
1228        if let Some(old_label) = old_label {
1229            let mut remove_old_key = false;
1230            if let Some(ref_base_info_list) = data.reference_bases.get_mut(&old_label) {
1231                if let Some(index) = ref_base_info_list
1232                    .iter()
1233                    .position(|info| info.owner_package_path == owner_package_path)
1234                {
1235                    ref_base_info_list.swap_remove(index);
1236                }
1237                remove_old_key = ref_base_info_list.is_empty();
1238            }
1239            if remove_old_key {
1240                data.reference_bases.remove(&old_label);
1241            }
1242        }
1243
1244        // Upsert the new entry for this owner package under the new label.
1245        let ref_base_info_list = data.reference_bases.entry(new_label).or_default();
1246        if let Some(existing_info) = ref_base_info_list
1247            .iter_mut()
1248            .find(|info| info.owner_package_path == owner_package_path)
1249        {
1250            existing_info.package_ref = package_ref_val;
1251            existing_info.package_ref_base = ref_base_attr;
1252        } else {
1253            ref_base_info_list.push(ReferenceBaseInfo {
1254                package_ref: package_ref_val,
1255                package_ref_base: ref_base_attr,
1256                owner_package_path,
1257            });
1258        }
1259    }
1260
1261    /// remove a reference base from the cache
1262    pub(crate) fn remove_reference_base(&self, label: &str, owner_package_path: &str) {
1263        let mut data = self.0.write();
1264        if let Some(ref_base_info_list) = data.reference_bases.get_mut(label)
1265            && let Some(index) = ref_base_info_list
1266                .iter()
1267                .position(|info| info.owner_package_path == owner_package_path)
1268        {
1269            ref_base_info_list.swap_remove(index);
1270            if ref_base_info_list.is_empty() {
1271                data.reference_bases.remove(label);
1272            }
1273        }
1274    }
1275}
1276
1277impl AutosarModelRaw {
1278    pub(crate) fn set_version(&mut self, new_ver: AutosarVersion) {
1279        let attribute_value = CharacterData::String(format!("http://autosar.org/schema/r4.0 {}", new_ver.filename()));
1280        let _ = self.root_element.0.write().set_attribute_internal(
1281            AttributeName::xsiSchemalocation,
1282            attribute_value,
1283            new_ver,
1284        );
1285    }
1286
1287    pub(crate) fn wrap(self) -> AutosarModel {
1288        AutosarModel(Arc::new(RwLock::new(self)))
1289    }
1290}
1291
1292impl std::fmt::Debug for AutosarModel {
1293    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1294        let model = self.0.read();
1295        // instead of the usual f.debug_struct().field().field() ...
1296        // this is disassembled here, in order to hold self.0.lock() as briefly as possible
1297        let rootelem = model.root_element.clone();
1298        let mut dbgstruct = f.debug_struct("AutosarModel");
1299        dbgstruct.field("root_element", &rootelem);
1300        dbgstruct.field("files", &model.files);
1301        dbgstruct.field("identifiables", &model.identifiables);
1302        dbgstruct.field("reference_origins", &model.reference_origins);
1303        dbgstruct.field("relative_reference_origins", &model.relative_reference_origins);
1304        dbgstruct.field("reference_bases", &model.reference_bases);
1305        dbgstruct.finish()
1306    }
1307}
1308
1309impl Default for AutosarModel {
1310    fn default() -> Self {
1311        Self::new()
1312    }
1313}
1314
1315impl PartialEq for AutosarModel {
1316    fn eq(&self, other: &Self) -> bool {
1317        Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
1318    }
1319}
1320
1321impl Eq for AutosarModel {}
1322
1323impl Hash for AutosarModel {
1324    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
1325        state.write_usize(Arc::as_ptr(&self.0) as usize);
1326    }
1327}
1328
1329impl WeakAutosarModel {
1330    pub(crate) fn upgrade(&self) -> Option<AutosarModel> {
1331        Weak::upgrade(&self.0).map(AutosarModel)
1332    }
1333}
1334
1335impl std::fmt::Debug for WeakAutosarModel {
1336    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1337        f.write_fmt(format_args!("AutosarModel:WeakRef {:p}", Weak::as_ptr(&self.0)))
1338    }
1339}
1340
1341#[cfg(test)]
1342mod test {
1343    use super::*;
1344    use tempfile::tempdir;
1345
1346    #[test]
1347    fn create_file() {
1348        let model = AutosarModel::new();
1349        let file = model.create_file("test", AutosarVersion::Autosar_00050);
1350        assert!(file.is_ok());
1351        // error: duplicate file name
1352        let file = model.create_file("test", AutosarVersion::Autosar_00050);
1353        assert!(file.is_err());
1354    }
1355
1356    #[test]
1357    fn load_buffer() {
1358        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1359        <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">
1360        <AR-PACKAGES>
1361          <AR-PACKAGE>
1362            <SHORT-NAME>Pkg</SHORT-NAME>
1363            <ELEMENTS>
1364              <SYSTEM><SHORT-NAME>Thing</SHORT-NAME></SYSTEM>
1365            </ELEMENTS>
1366          </AR-PACKAGE>
1367        </AR-PACKAGES></AUTOSAR>"#;
1368        const FILEBUF2: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1369        <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">
1370        <AR-PACKAGES>
1371          <AR-PACKAGE><SHORT-NAME>OtherPkg</SHORT-NAME></AR-PACKAGE>
1372        </AR-PACKAGES></AUTOSAR>"#;
1373        const FILEBUF3: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1374        <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">
1375        <AR-PACKAGES>
1376          <AR-PACKAGE>
1377            <SHORT-NAME>Pkg</SHORT-NAME>
1378            <ELEMENTS>
1379            <APPLICATION-PRIMITIVE-DATA-TYPE><SHORT-NAME>Thing</SHORT-NAME></APPLICATION-PRIMITIVE-DATA-TYPE>
1380            </ELEMENTS>
1381          </AR-PACKAGE>
1382        </AR-PACKAGES></AUTOSAR>"#;
1383        const NON_ARXML: &str = "The quick brown fox jumps over the lazy dog";
1384        let model = AutosarModel::new();
1385        // succefully load a buffer
1386        let result = model.load_buffer(FILEBUF.as_bytes(), "test", true);
1387        assert!(result.is_ok());
1388        // succefully load a second buffer
1389        let result = model.load_buffer(FILEBUF2.as_bytes(), "other", true);
1390        assert!(result.is_ok());
1391        // error: duplicate file name
1392        let result = model.load_buffer(FILEBUF.as_bytes(), "test", true);
1393        assert!(result.is_err());
1394        // error: overlapping autosar paths
1395        let result = model.load_buffer(FILEBUF3.as_bytes(), "test2", true);
1396        assert!(result.is_err());
1397        // error: not arxml data
1398        let result = model.load_buffer(NON_ARXML.as_bytes(), "nonsense", true);
1399        assert!(result.is_err());
1400    }
1401
1402    #[test]
1403    fn load_file() {
1404        let dir = tempdir().unwrap();
1405
1406        let model = AutosarModel::new();
1407        let filename = dir.path().with_file_name("nonexistent.arxml");
1408        assert!(model.load_file(&filename, true).is_err());
1409
1410        let filename = dir.path().with_file_name("test.arxml");
1411        model.create_file(&filename, AutosarVersion::LATEST).unwrap();
1412        model
1413            .root_element()
1414            .create_sub_element(ElementName::ArPackages)
1415            .and_then(|ap| ap.create_named_sub_element(ElementName::ArPackage, "Pkg"))
1416            .unwrap();
1417        model.write().unwrap();
1418
1419        assert!(filename.exists());
1420
1421        // careate a new model without data
1422        let model = AutosarModel::new();
1423        model.load_file(&filename, true).unwrap();
1424        let el_pkg = model.get_element_by_path("/Pkg");
1425        assert!(el_pkg.is_some());
1426    }
1427
1428    #[test]
1429    fn data_merge() {
1430        const FILEBUF1: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1431        <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">
1432        <AR-PACKAGES>
1433          <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
1434            <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
1435              <SHORT-NAME>BswModuleValues</SHORT-NAME>
1436              <PARAMETER-VALUES>
1437                <ECUC-NUMERICAL-PARAM-VALUE>
1438                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
1439                </ECUC-NUMERICAL-PARAM-VALUE>
1440                <ECUC-NUMERICAL-PARAM-VALUE>
1441                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
1442                </ECUC-NUMERICAL-PARAM-VALUE>
1443                <ECUC-NUMERICAL-PARAM-VALUE>
1444                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_C</DEFINITION-REF>
1445                </ECUC-NUMERICAL-PARAM-VALUE>
1446              </PARAMETER-VALUES>
1447            </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
1448          </ELEMENTS></AR-PACKAGE>
1449          <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
1450          <AR-PACKAGE><SHORT-NAME>Pkg_C</SHORT-NAME></AR-PACKAGE>
1451        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
1452        const FILEBUF2: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1453        <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">
1454        <AR-PACKAGES>
1455          <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
1456          <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
1457            <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
1458              <SHORT-NAME>BswModuleValues</SHORT-NAME>
1459              <PARAMETER-VALUES>
1460                <ECUC-NUMERICAL-PARAM-VALUE>
1461                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
1462                </ECUC-NUMERICAL-PARAM-VALUE>
1463                <ECUC-NUMERICAL-PARAM-VALUE>
1464                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
1465                </ECUC-NUMERICAL-PARAM-VALUE>
1466              </PARAMETER-VALUES>
1467            </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
1468          </ELEMENTS></AR-PACKAGE>
1469        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
1470        // test with re-ordered identifiable elements and re-ordered BSW parameter values
1471        // file2 is a subset of file1, so the total number of elements does not increase
1472        let model = AutosarModel::new();
1473        let (file1, _) = model.load_buffer(FILEBUF1, "test1", true).unwrap();
1474        let file1_elemcount = file1.elements_dfs().count();
1475        let (file2, _) = model.load_buffer(FILEBUF2, "test2", true).unwrap();
1476        let file2_elemcount = file2.elements_dfs().count();
1477        let model_elemcount = model.elements_dfs().count();
1478        assert_eq!(file1_elemcount, model_elemcount);
1479        assert!(file1_elemcount > file2_elemcount);
1480        // verify file membership after merging
1481        let (local, fileset) = model.root_element().file_membership().unwrap();
1482        assert!(local);
1483        assert_eq!(fileset.len(), 2);
1484
1485        let el_pkg_c = model.get_element_by_path("/Pkg_C").unwrap();
1486        let (local, fileset) = el_pkg_c.file_membership().unwrap();
1487        assert!(local);
1488        assert_eq!(fileset.len(), 1);
1489        let el_npv2 = model
1490            .get_element_by_path("/Pkg_A/BswModule/BswModuleValues")
1491            .and_then(|bmv| bmv.get_sub_element(ElementName::ParameterValues))
1492            .and_then(|pv| pv.get_sub_element_at(2))
1493            .unwrap();
1494        let (loc, fm) = el_npv2.file_membership().unwrap();
1495        assert!(loc);
1496        assert_eq!(fm.len(), 1);
1497
1498        // the following two files diverge on the TIMING-RESOURCE element
1499        // this is not permitted, because SYSTEM-TIMING is not splittable
1500        const ERRFILE1: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1501        <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">
1502        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
1503          <ELEMENTS>
1504            <SYSTEM-TIMING>
1505              <SHORT-NAME>SystemTimings</SHORT-NAME>
1506              <CATEGORY>CAT</CATEGORY>
1507              <TIMING-RESOURCE>
1508                <SHORT-NAME>Name_One</SHORT-NAME>
1509              </TIMING-RESOURCE>
1510            </SYSTEM-TIMING>
1511          </ELEMENTS>
1512        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
1513        const ERRFILE2: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1514        <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">
1515        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
1516          <ELEMENTS>
1517            <SYSTEM-TIMING>
1518              <SHORT-NAME>SystemTimings</SHORT-NAME>
1519              <TIMING-RESOURCE>
1520                <SHORT-NAME>Name_Two</SHORT-NAME>
1521              </TIMING-RESOURCE>
1522            </SYSTEM-TIMING>
1523          </ELEMENTS>
1524        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
1525        let model = AutosarModel::new();
1526        let result = model.load_buffer(ERRFILE1, "test1", true);
1527        assert!(result.is_ok());
1528        let result = model.load_buffer(ERRFILE2, "test2", true);
1529        let error = result.unwrap_err();
1530        assert!(matches!(error, AutosarDataError::InvalidFileMerge { .. }));
1531
1532        // diverging files, where each file uses a different element from a Choice set.
1533        // In this case the COMPU-SCALE in ERRFILE3 uses COMPU-CONST while ERRFILE4 uses COMPU-RATIONAL-COEFFS.
1534        // This is not permitted, because the COMPU-SCALE can only contain one or the other.
1535        const ERRFILE3: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1536        <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">
1537        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
1538          <ELEMENTS>
1539            <COMPU-METHOD><SHORT-NAME>compu</SHORT-NAME>
1540              <COMPU-INTERNAL-TO-PHYS>
1541                <COMPU-SCALES>
1542                  <COMPU-SCALE><COMPU-CONST></COMPU-CONST></COMPU-SCALE>
1543                </COMPU-SCALES>
1544              </COMPU-INTERNAL-TO-PHYS>
1545            </COMPU-METHOD>
1546          </ELEMENTS>
1547        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
1548        const ERRFILE4: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1549        <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">
1550        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
1551          <ELEMENTS>
1552            <COMPU-METHOD><SHORT-NAME>compu</SHORT-NAME>
1553              <COMPU-INTERNAL-TO-PHYS>
1554                <COMPU-SCALES>
1555                  <COMPU-SCALE><COMPU-RATIONAL-COEFFS></COMPU-RATIONAL-COEFFS></COMPU-SCALE>
1556                </COMPU-SCALES>
1557              </COMPU-INTERNAL-TO-PHYS>
1558            </COMPU-METHOD>
1559          </ELEMENTS>
1560        </AR-PACKAGE></AR-PACKAGES></AUTOSAR>"#.as_bytes();
1561        let model = AutosarModel::new();
1562        let result = model.load_buffer(ERRFILE3, "test3", true);
1563        assert!(result.is_ok());
1564        let result = model.load_buffer(ERRFILE4, "test4", true);
1565        let error = result.unwrap_err();
1566        assert!(matches!(error, AutosarDataError::InvalidFileMerge { .. }));
1567
1568        // non-overlapping files
1569        const FILEBUF3: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1570        <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">
1571        <AR-PACKAGES>
1572          <AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME></AR-PACKAGE>
1573          <AR-PACKAGE><SHORT-NAME>Package2</SHORT-NAME></AR-PACKAGE>
1574        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
1575        const FILEBUF4: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1576        <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">
1577        <AR-PACKAGES>
1578        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
1579        let model_a = AutosarModel::new();
1580        model_a.load_buffer(FILEBUF3, "test5", true).unwrap();
1581        model_a.load_buffer(FILEBUF4, "test6", true).unwrap();
1582        // load the files into model_b in reverse order
1583        let model_b = AutosarModel::new();
1584        model_b.load_buffer(FILEBUF4, "test5", true).unwrap();
1585        model_b.load_buffer(FILEBUF3, "test6", true).unwrap();
1586        // the two models should be equal
1587        model_a.sort();
1588        let model_a_txt = model_a.root_element().serialize();
1589        model_b.sort();
1590        let model_b_txt = model_b.root_element().serialize();
1591        assert_eq!(model_a_txt, model_b_txt);
1592    }
1593
1594    #[test]
1595    fn remove_file() {
1596        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1597        <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">
1598        <AR-PACKAGES>
1599        <AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME></AR-PACKAGE>
1600        </AR-PACKAGES></AUTOSAR>"#;
1601        const FILEBUF2: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1602        <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">
1603        <AR-PACKAGES>
1604        <AR-PACKAGE><SHORT-NAME>Package</SHORT-NAME>
1605        <ELEMENTS><CAN-CLUSTER><SHORT-NAME>CAN_Cluster</SHORT-NAME></CAN-CLUSTER></ELEMENTS>
1606        </AR-PACKAGE>
1607        </AR-PACKAGES></AUTOSAR>"#;
1608        const FILEBUF3: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1609        <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">
1610        <AR-PACKAGES>
1611        <AR-PACKAGE><SHORT-NAME>Package2</SHORT-NAME>
1612        <ELEMENTS><SYSTEM><SHORT-NAME>System</SHORT-NAME>
1613        <FIBEX-ELEMENTS><FIBEX-ELEMENT-REF-CONDITIONAL>
1614            <FIBEX-ELEMENT-REF DEST="CAN-CLUSTER">/Package/CAN_Cluster</FIBEX-ELEMENT-REF>
1615        </FIBEX-ELEMENT-REF-CONDITIONAL></FIBEX-ELEMENTS>
1616        </SYSTEM></ELEMENTS></AR-PACKAGE>
1617        </AR-PACKAGES></AUTOSAR>"#;
1618        // easy case: remove the only file
1619        let model = AutosarModel::new();
1620        let (file, _) = model.load_buffer(FILEBUF.as_bytes(), "test", true).unwrap();
1621        assert_eq!(model.files().count(), 1);
1622        assert_eq!(model.identifiable_elements().count(), 1);
1623        model.remove_file(&file);
1624        assert_eq!(model.files().count(), 0);
1625        assert_eq!(model.identifiable_elements().count(), 0);
1626        // complicated: remove one of several files
1627        let model = AutosarModel::new();
1628        model.load_buffer(FILEBUF.as_bytes(), "test1", true).unwrap();
1629        assert_eq!(model.files().count(), 1);
1630        let modeltxt_1 = model.root_element().serialize();
1631        let (file2, _) = model.load_buffer(FILEBUF2.as_bytes(), "test2", true).unwrap();
1632        assert_eq!(model.files().count(), 2);
1633        let modeltxt_1_2 = model.root_element().serialize();
1634        assert_ne!(modeltxt_1, modeltxt_1_2);
1635        let (file3, _) = model.load_buffer(FILEBUF3.as_bytes(), "test3", true).unwrap();
1636        assert_eq!(model.files().count(), 3);
1637        let modeltxt_1_2_3 = model.root_element().serialize();
1638        assert_ne!(modeltxt_1_2, modeltxt_1_2_3);
1639        model.get_element_by_path("/Package2/System").unwrap();
1640        model.remove_file(&file3);
1641        // the serialized text of the model after deleting file 3 should be the same as it was before loading file 3
1642        let modeltxt_1_2_x = model.root_element().serialize();
1643        assert_eq!(modeltxt_1_2, modeltxt_1_2_x);
1644        model.remove_file(&file2);
1645        // 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
1646        let modeltxt_1_x_x = model.root_element().serialize();
1647        assert_eq!(modeltxt_1, modeltxt_1_x_x);
1648        assert_eq!(model.files().count(), 1);
1649    }
1650
1651    #[test]
1652    fn remove_last_file_clears_relative_reference_origins() {
1653        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1654<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">
1655    <AR-PACKAGES>
1656        <AR-PACKAGE>
1657            <SHORT-NAME>BasePkg</SHORT-NAME>
1658            <ELEMENTS>
1659                <ECU-INSTANCE>
1660                    <SHORT-NAME>Ecu</SHORT-NAME>
1661                </ECU-INSTANCE>
1662            </ELEMENTS>
1663        </AR-PACKAGE>
1664        <AR-PACKAGE>
1665            <SHORT-NAME>RefPkg</SHORT-NAME>
1666            <REFERENCE-BASES>
1667                <REFERENCE-BASE>
1668                    <SHORT-LABEL>BaseA</SHORT-LABEL>
1669                    <PACKAGE-REF DEST="AR-PACKAGE">/BasePkg</PACKAGE-REF>
1670                </REFERENCE-BASE>
1671            </REFERENCE-BASES>
1672            <ELEMENTS>
1673                <SYSTEM>
1674                    <SHORT-NAME>Sys</SHORT-NAME>
1675                    <FIBEX-ELEMENTS>
1676                        <FIBEX-ELEMENT-REF-CONDITIONAL>
1677                            <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="BaseA">Ecu</FIBEX-ELEMENT-REF>
1678                        </FIBEX-ELEMENT-REF-CONDITIONAL>
1679                    </FIBEX-ELEMENTS>
1680                </SYSTEM>
1681            </ELEMENTS>
1682        </AR-PACKAGE>
1683    </AR-PACKAGES>
1684</AUTOSAR>"#
1685                        .as_bytes();
1686
1687        let model = AutosarModel::new();
1688        let (file, _) = model.load_buffer(FILEBUF, "test", true).unwrap();
1689
1690        assert!(!model.0.read().relative_reference_origins.is_empty());
1691        model.remove_file(&file);
1692
1693        assert!(model.0.read().relative_reference_origins.is_empty());
1694    }
1695
1696    #[test]
1697    fn refcount() {
1698        let model = AutosarModel::default();
1699        let weak = model.downgrade();
1700        let project2 = weak.upgrade();
1701        assert_eq!(Arc::strong_count(&model.0), 2);
1702        assert_eq!(model, project2.unwrap());
1703    }
1704
1705    #[test]
1706    fn identifiables_iterator() {
1707        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1708        <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">
1709        <AR-PACKAGES>
1710        <AR-PACKAGE><SHORT-NAME>OuterPackage1</SHORT-NAME>
1711            <AR-PACKAGES>
1712                <AR-PACKAGE><SHORT-NAME>InnerPackage1</SHORT-NAME></AR-PACKAGE>
1713                <AR-PACKAGE><SHORT-NAME>InnerPackage2</SHORT-NAME></AR-PACKAGE>
1714            </AR-PACKAGES>
1715        </AR-PACKAGE>
1716        <AR-PACKAGE><SHORT-NAME>OuterPackage2</SHORT-NAME>
1717            <AR-PACKAGES>
1718                <AR-PACKAGE><SHORT-NAME>InnerPackage1</SHORT-NAME></AR-PACKAGE>
1719                <AR-PACKAGE><SHORT-NAME>InnerPackage2</SHORT-NAME></AR-PACKAGE>
1720            </AR-PACKAGES>
1721        </AR-PACKAGE>
1722        </AR-PACKAGES></AUTOSAR>"#;
1723        let model = AutosarModel::new();
1724        model.load_buffer(FILEBUF.as_bytes(), "test", true).unwrap();
1725        let mut identifiable_elements = model.identifiable_elements().collect::<Vec<_>>();
1726        identifiable_elements.sort_by(|a, b| a.0.cmp(&b.0));
1727        assert_eq!(identifiable_elements[0].0, "/OuterPackage1");
1728        assert_eq!(identifiable_elements[1].0, "/OuterPackage1/InnerPackage1");
1729        assert_eq!(identifiable_elements[2].0, "/OuterPackage1/InnerPackage2");
1730        assert_eq!(identifiable_elements[3].0, "/OuterPackage2");
1731        assert_eq!(identifiable_elements[4].0, "/OuterPackage2/InnerPackage1");
1732        assert_eq!(identifiable_elements[5].0, "/OuterPackage2/InnerPackage2");
1733    }
1734
1735    #[test]
1736    fn check_references() {
1737        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
1738        <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">
1739        <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Pkg</SHORT-NAME>
1740            <ELEMENTS>
1741                <SYSTEM><SHORT-NAME>System</SHORT-NAME>
1742                    <FIBEX-ELEMENTS>
1743                        <FIBEX-ELEMENT-REF-CONDITIONAL>
1744                            <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE">/Pkg/EcuInstance</FIBEX-ELEMENT-REF>
1745                        </FIBEX-ELEMENT-REF-CONDITIONAL>
1746                        <FIBEX-ELEMENT-REF-CONDITIONAL>
1747                            <FIBEX-ELEMENT-REF DEST="I-SIGNAL-I-PDU">/Some/Invalid/Path</FIBEX-ELEMENT-REF>
1748                        </FIBEX-ELEMENT-REF-CONDITIONAL>
1749                        <FIBEX-ELEMENT-REF-CONDITIONAL>
1750                            <FIBEX-ELEMENT-REF DEST="I-SIGNAL">/Pkg/System</FIBEX-ELEMENT-REF>
1751                        </FIBEX-ELEMENT-REF-CONDITIONAL>
1752                    </FIBEX-ELEMENTS>
1753                </SYSTEM>
1754                <ECU-INSTANCE><SHORT-NAME>EcuInstance</SHORT-NAME></ECU-INSTANCE>
1755            </ELEMENTS>
1756        </AR-PACKAGE>
1757        </AR-PACKAGES></AUTOSAR>"#;
1758        let model = AutosarModel::new();
1759        model.load_buffer(FILEBUF.as_bytes(), "test", true).unwrap();
1760        let el_system = model.get_element_by_path("/Pkg/System").unwrap();
1761        let el_fibex_elements = el_system.get_sub_element(ElementName::FibexElements).unwrap();
1762        let el_fibex_element_ref = el_fibex_elements
1763            .create_sub_element(ElementName::FibexElementRefConditional)
1764            .and_then(|ferc| ferc.create_sub_element(ElementName::FibexElementRef))
1765            .unwrap();
1766        el_fibex_element_ref.set_character_data("/Pkg/System").unwrap();
1767        // the test data contains 4 references to 3 distinct items:
1768        // - to /Pkg/EcuInstance (VALID)
1769        // - to /Some/Invalid/Path (INVALID)
1770        // - to /Pkg/System, with DEST=I-SIGNAL (INVALID)
1771        // - to /Pkg/System, without DEST (INVALID)
1772        assert_eq!(model.0.read().reference_origins.len(), 3);
1773
1774        // confirm that the first reference, to EcuInstance, is valid
1775        let el_fbx_ref1 = el_fibex_elements
1776            .get_sub_element_at(0)
1777            .and_then(|ferc| ferc.get_sub_element(ElementName::FibexElementRef))
1778            .unwrap();
1779        assert_eq!(
1780            el_fbx_ref1.get_reference_target().unwrap().element_name(),
1781            ElementName::EcuInstance
1782        );
1783
1784        let invalid_refs = model
1785            .check_references()
1786            .iter()
1787            .filter_map(WeakElement::upgrade)
1788            .collect::<Vec<_>>();
1789        assert_eq!(invalid_refs.len(), 3);
1790        let ref0 = &invalid_refs[0];
1791        assert_eq!(ref0.element_name(), ElementName::FibexElementRef);
1792        let refpath = ref0.character_data().and_then(|cdata| cdata.string_value()).unwrap();
1793        // there is no defined order in which the references will be checked, so any of the three broken refs could be returned first
1794        assert!(refpath == "/Pkg/System" || refpath == "/Some/Invalid/Path");
1795
1796        model.get_element_by_path("/Pkg/EcuInstance").unwrap();
1797        let refs = model.get_references_to("/Pkg/EcuInstance");
1798        assert_eq!(refs.len(), 1);
1799        let refs = model.get_references_to("nonexistent");
1800        assert!(refs.is_empty());
1801    }
1802
1803    #[test]
1804    fn serialize_files() {
1805        let model = AutosarModel::default();
1806        let file1 = model.create_file("filename1", AutosarVersion::Autosar_00042).unwrap();
1807        let file2 = model.create_file("filename2", AutosarVersion::Autosar_00042).unwrap();
1808
1809        let result = model.serialize_files();
1810        assert_eq!(result.len(), 2);
1811        assert_eq!(
1812            result.get(&PathBuf::from("filename1")).unwrap(),
1813            &file1.serialize().unwrap()
1814        );
1815        assert_eq!(
1816            result.get(&PathBuf::from("filename2")).unwrap(),
1817            &file2.serialize().unwrap()
1818        );
1819    }
1820
1821    #[test]
1822    fn duplicate() {
1823        let model = AutosarModel::new();
1824        let file1 = model.create_file("filename1", AutosarVersion::Autosar_00042).unwrap();
1825        let file2 = model.create_file("filename2", AutosarVersion::Autosar_00042).unwrap();
1826        let el_ar_packages = model
1827            .root_element()
1828            .create_sub_element(ElementName::ArPackages)
1829            .unwrap();
1830        let el_pkg1 = el_ar_packages
1831            .create_named_sub_element(ElementName::ArPackage, "pkg1")
1832            .unwrap();
1833        let el_pkg2 = el_ar_packages
1834            .create_named_sub_element(ElementName::ArPackage, "pkg2")
1835            .unwrap();
1836
1837        assert_eq!(el_ar_packages.file_membership().unwrap().1.len(), 2);
1838        el_pkg1.remove_from_file(&file2).unwrap();
1839        assert_eq!(el_pkg1.file_membership().unwrap().1.len(), 1);
1840        el_pkg2.remove_from_file(&file1).unwrap();
1841        assert_eq!(el_pkg2.file_membership().unwrap().1.len(), 1);
1842
1843        let model2 = model.duplicate().unwrap();
1844        assert_eq!(model2.files().count(), 2);
1845        let mut files_iter = model2.files();
1846        // get the files out of model 2
1847        let mut model2_file1 = files_iter.next().unwrap();
1848        let mut model2_file2 = files_iter.next().unwrap();
1849        // the iterator could return the files in any order - make sure that model2_file1 corresponds to file1
1850        if model2_file1.filename() != file1.filename() {
1851            std::mem::swap(&mut model2_file1, &mut model2_file2);
1852        }
1853
1854        assert_eq!(file1.filename(), model2_file1.filename());
1855        assert_eq!(file2.filename(), model2_file2.filename());
1856        assert_eq!(file1.serialize().unwrap(), model2_file1.serialize().unwrap());
1857        assert_eq!(file2.serialize().unwrap(), model2_file2.serialize().unwrap());
1858    }
1859
1860    #[test]
1861    fn write() {
1862        let model = AutosarModel::default();
1863        // write an empty model, it does nothing since there are no files
1864        model.write().unwrap();
1865
1866        let dir = tempdir().unwrap();
1867        let filename = dir.path().with_file_name("new.arxml");
1868        model.create_file(&filename, AutosarVersion::LATEST).unwrap();
1869        model.write().unwrap();
1870        assert!(filename.exists());
1871
1872        let filename = PathBuf::from("nonexistent/dir/some_file.arxml");
1873        let model = AutosarModel::default();
1874        // creating an ArxmlFile with a non-existent directory is not an error
1875        model.create_file(&filename, AutosarVersion::LATEST).unwrap();
1876        // the write operation will fail, because the directory does not exist
1877        let result = model.write();
1878        assert!(result.is_err());
1879    }
1880
1881    #[test]
1882    fn traits() {
1883        // AutosarModel: Debug, Clone, Hash
1884        let model = AutosarModel::new();
1885        let model_cloned = model.clone();
1886        assert_eq!(model, model_cloned);
1887        assert_eq!(format!("{model:#?}"), format!("{model_cloned:#?}"));
1888        let mut hashset = HashSet::<AutosarModel>::new();
1889        hashset.insert(model);
1890        let inserted = hashset.insert(model_cloned);
1891        assert!(!inserted);
1892
1893        // CharacterData
1894        let cdata = CharacterData::String("x".to_string());
1895        let cdata2 = cdata.clone();
1896        assert_eq!(cdata, cdata2);
1897        assert_eq!(format!("{cdata:#?}"), format!("{cdata2:#?}"));
1898
1899        // ContentType
1900        let ct: ContentType = ContentType::Elements;
1901        let ct2 = ct;
1902        assert_eq!(ct, ct2);
1903        assert_eq!(format!("{ct:#?}"), format!("{ct2:#?}"));
1904    }
1905
1906    #[test]
1907    fn elements_dfs_with_max_depth() {
1908        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
1909        <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">
1910        <AR-PACKAGES>
1911          <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
1912            <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
1913              <SHORT-NAME>BswModuleValues</SHORT-NAME>
1914              <PARAMETER-VALUES>
1915                <ECUC-NUMERICAL-PARAM-VALUE>
1916                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
1917                </ECUC-NUMERICAL-PARAM-VALUE>
1918                <ECUC-NUMERICAL-PARAM-VALUE>
1919                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
1920                </ECUC-NUMERICAL-PARAM-VALUE>
1921                <ECUC-NUMERICAL-PARAM-VALUE>
1922                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_C</DEFINITION-REF>
1923                </ECUC-NUMERICAL-PARAM-VALUE>
1924              </PARAMETER-VALUES>
1925            </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
1926          </ELEMENTS></AR-PACKAGE>
1927          <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
1928          <AR-PACKAGE><SHORT-NAME>Pkg_C</SHORT-NAME></AR-PACKAGE>
1929        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
1930        let model = AutosarModel::new();
1931        let (_, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
1932        let all_count = model.elements_dfs().count();
1933        let lvl2_count = model.elements_dfs_with_max_depth(2).count();
1934        assert!(all_count > lvl2_count);
1935        for elem in model.elements_dfs_with_max_depth(2) {
1936            assert!(elem.0 <= 2);
1937        }
1938    }
1939
1940    #[test]
1941    fn model_merge() {
1942        // from github issue #24; test files provided by FlTr
1943        const FILE_A: &[u8] = br#"<?xml version="1.0" encoding="utf-8"?>
1944<AUTOSAR xmlns="http://autosar.org/schema/r4.0"
1945         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1946         xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00048.xsd">
1947  <AR-PACKAGES>
1948    <AR-PACKAGE>
1949      <SHORT-NAME>EcucModuleConfigurationValuess</SHORT-NAME>
1950      <ELEMENTS>
1951        <ECUC-MODULE-CONFIGURATION-VALUES>
1952          <SHORT-NAME>A</SHORT-NAME>
1953          <DEFINITION-REF DEST="ECUC-MODULE-DEF">/AUTOSAR_A</DEFINITION-REF>
1954          <CONTAINERS>
1955            <ECUC-CONTAINER-VALUE>
1956              <SHORT-NAME>AB</SHORT-NAME>
1957              <DEFINITION-REF DEST="ECUC-PARAM-CONF-CONTAINER-DEF">/AUTOSAR_A/B</DEFINITION-REF>
1958              <PARAMETER-VALUES>
1959                <ECUC-NUMERICAL-PARAM-VALUE>
1960                  <DEFINITION-REF DEST="ECUC-FLOAT-PARAM-DEF">/AUTOSAR_A/B/D</DEFINITION-REF>
1961                  <VALUE>0.01</VALUE>
1962                </ECUC-NUMERICAL-PARAM-VALUE>
1963                <ECUC-TEXTUAL-PARAM-VALUE>
1964                  <DEFINITION-REF DEST="ECUC-ENUMERATION-PARAM-DEF">/AUTOSAR_A/B/E</DEFINITION-REF>
1965                  <VALUE>ABC42</VALUE>
1966                </ECUC-TEXTUAL-PARAM-VALUE>
1967              </PARAMETER-VALUES>
1968            </ECUC-CONTAINER-VALUE>
1969          </CONTAINERS>
1970        </ECUC-MODULE-CONFIGURATION-VALUES>
1971      </ELEMENTS>
1972    </AR-PACKAGE>
1973  </AR-PACKAGES>
1974</AUTOSAR>
1975        "#;
1976
1977        const FILE_B: &[u8] = br#"<?xml version="1.0" encoding="utf-8"?>
1978<AUTOSAR xmlns="http://autosar.org/schema/r4.0"
1979         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
1980         xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_00048.xsd">
1981  <AR-PACKAGES>
1982    <AR-PACKAGE>
1983      <SHORT-NAME>EcucModuleConfigurationValuess</SHORT-NAME>
1984      <ELEMENTS>
1985        <ECUC-MODULE-CONFIGURATION-VALUES>
1986          <SHORT-NAME>A</SHORT-NAME>
1987          <DEFINITION-REF DEST="ECUC-MODULE-DEF">/AUTOSAR_A</DEFINITION-REF>
1988          <CONTAINERS>
1989            <ECUC-CONTAINER-VALUE>
1990              <SHORT-NAME>AB</SHORT-NAME>
1991              <DEFINITION-REF DEST="ECUC-PARAM-CONF-CONTAINER-DEF">/AUTOSAR_A/B</DEFINITION-REF>
1992              <PARAMETER-VALUES>
1993                <ECUC-NUMERICAL-PARAM-VALUE>
1994                  <DEFINITION-REF DEST="ECUC-INTEGER-PARAM-DEF">/AUTOSAR_A/B/C</DEFINITION-REF>
1995                  <VALUE>0</VALUE>
1996                </ECUC-NUMERICAL-PARAM-VALUE>
1997                <ECUC-NUMERICAL-PARAM-VALUE>
1998                  <DEFINITION-REF DEST="ECUC-FLOAT-PARAM-DEF">/AUTOSAR_A/B/D</DEFINITION-REF>
1999                  <VALUE>0.01</VALUE>
2000                </ECUC-NUMERICAL-PARAM-VALUE>
2001              </PARAMETER-VALUES>
2002            </ECUC-CONTAINER-VALUE>
2003          </CONTAINERS>
2004        </ECUC-MODULE-CONFIGURATION-VALUES>
2005      </ELEMENTS>
2006    </AR-PACKAGE>
2007  </AR-PACKAGES>
2008</AUTOSAR>"#;
2009
2010        // loading these files must not hang, regardless of the order
2011        let model = AutosarModel::new();
2012        let (_, _) = model.load_buffer(FILE_A, "file_a", true).unwrap();
2013        let (_, _) = model.load_buffer(FILE_B, "file_b", true).unwrap();
2014        // sort the model to ensure that the serialized text is the same
2015        model.sort();
2016        let model_txt = model.root_element().serialize();
2017
2018        let model2 = AutosarModel::new();
2019        let (_, _) = model2.load_buffer(FILE_B, "file_b", true).unwrap();
2020        let (_, _) = model2.load_buffer(FILE_A, "file_a", true).unwrap();
2021        // sort the model to ensure that the serialized text is the same
2022        model2.sort();
2023        let model2_txt = model2.root_element().serialize();
2024
2025        assert_eq!(model_txt, model2_txt);
2026    }
2027
2028    #[test]
2029    fn model_merge_2() {
2030        // regression test for github issue #30
2031        const FILEBUF1: &[u8] = br#"<?xml version="1.0" encoding="UTF-8"?>
2032<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">
2033  <AR-PACKAGES>
2034    <AR-PACKAGE>
2035      <SHORT-NAME>BSWMD_Package</SHORT-NAME>
2036      <ELEMENTS>
2037        <BSW-MODULE-DESCRIPTION>
2038          <SHORT-NAME>BSWMD</SHORT-NAME>
2039          <IMPLEMENTED-ENTRYS>
2040            <BSW-MODULE-ENTRY-REF-CONDITIONAL>
2041              <BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_A0</BSW-MODULE-ENTRY-REF>
2042            </BSW-MODULE-ENTRY-REF-CONDITIONAL>
2043          </IMPLEMENTED-ENTRYS>
2044        </BSW-MODULE-DESCRIPTION>
2045      </ELEMENTS>
2046    </AR-PACKAGE>
2047  </AR-PACKAGES>
2048</AUTOSAR>"#;
2049        const FILEBUF2: &[u8] = br#"<?xml version="1.0" encoding="UTF-8"?>
2050<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">
2051  <AR-PACKAGES>
2052    <AR-PACKAGE>
2053      <SHORT-NAME>BSWMD_Package</SHORT-NAME>
2054      <ELEMENTS>
2055        <BSW-MODULE-DESCRIPTION>
2056          <SHORT-NAME>BSWMD</SHORT-NAME>
2057          <IMPLEMENTED-ENTRYS>
2058            <BSW-MODULE-ENTRY-REF-CONDITIONAL>
2059              <BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_B0</BSW-MODULE-ENTRY-REF>
2060            </BSW-MODULE-ENTRY-REF-CONDITIONAL>
2061            <BSW-MODULE-ENTRY-REF-CONDITIONAL>
2062              <BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_B1</BSW-MODULE-ENTRY-REF>
2063            </BSW-MODULE-ENTRY-REF-CONDITIONAL>
2064          </IMPLEMENTED-ENTRYS>
2065        </BSW-MODULE-DESCRIPTION>
2066      </ELEMENTS>
2067    </AR-PACKAGE>
2068  </AR-PACKAGES>
2069</AUTOSAR>"#;
2070
2071        let model = AutosarModel::new();
2072        let (_, _) = model.load_buffer(FILEBUF1, "file1", true).unwrap();
2073        let (_, _) = model.load_buffer(FILEBUF2, "file2", true).unwrap();
2074
2075        let a0_refs = model.get_references_to("/path/to/entry_A0");
2076        assert!(a0_refs.len() == 1);
2077        assert!(a0_refs[0].upgrade().is_some());
2078        let b0_refs = model.get_references_to("/path/to/entry_B0");
2079        assert!(b0_refs.len() == 1);
2080        assert!(b0_refs[0].upgrade().is_some());
2081        let b1_refs = model.get_references_to("/path/to/entry_B1");
2082        assert!(b1_refs.len() == 1);
2083        assert!(b1_refs[0].upgrade().is_some());
2084    }
2085
2086    #[test]
2087    fn complex_reference_bases() {
2088        const FILEBUF1: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
2089<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">
2090  <AR-PACKAGES>
2091    <AR-PACKAGE><SHORT-NAME>BasesPkg</SHORT-NAME>
2092      <REFERENCE-BASES>
2093        <REFERENCE-BASE>
2094          <SHORT-LABEL>BaseA</SHORT-LABEL>
2095          <PACKAGE-REF DEST="AR-PACKAGE">/ContentPkg</PACKAGE-REF>
2096        </REFERENCE-BASE>
2097        <REFERENCE-BASE>
2098          <SHORT-LABEL>BaseC</SHORT-LABEL>
2099          <PACKAGE-REF DEST="AR-PACKAGE">/ContentPkg2</PACKAGE-REF>
2100        </REFERENCE-BASE>
2101      </REFERENCE-BASES>
2102      <AR-PACKAGES>
2103        <AR-PACKAGE><SHORT-NAME>SubPackage</SHORT-NAME>
2104          <REFERENCE-BASES>
2105            <REFERENCE-BASE>
2106              <SHORT-LABEL>BaseB</SHORT-LABEL>
2107              <PACKAGE-REF DEST="AR-PACKAGE" BASE="BaseA">SubPackage</PACKAGE-REF>
2108            </REFERENCE-BASE>
2109          </REFERENCE-BASES>
2110          <ELEMENTS>
2111            <SYSTEM><SHORT-NAME>System</SHORT-NAME>
2112              <FIBEX-ELEMENTS>
2113                <FIBEX-ELEMENT-REF-CONDITIONAL>
2114                  <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="BaseB">Ecu</FIBEX-ELEMENT-REF>
2115                </FIBEX-ELEMENT-REF-CONDITIONAL>
2116                <FIBEX-ELEMENT-REF-CONDITIONAL>
2117                  <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="BaseC">Ecu</FIBEX-ELEMENT-REF>
2118                </FIBEX-ELEMENT-REF-CONDITIONAL>
2119              </FIBEX-ELEMENTS>
2120            </SYSTEM>
2121          </ELEMENTS>
2122        </AR-PACKAGE>
2123      </AR-PACKAGES>
2124    </AR-PACKAGE>
2125    <AR-PACKAGE><SHORT-NAME>ContentPkg</SHORT-NAME>
2126      <AR-PACKAGES>
2127        <AR-PACKAGE><SHORT-NAME>SubPackage</SHORT-NAME>
2128          <ELEMENTS>
2129            <ECU-INSTANCE><SHORT-NAME>Ecu</SHORT-NAME></ECU-INSTANCE>
2130          </ELEMENTS>
2131        </AR-PACKAGE>
2132      </AR-PACKAGES>
2133    </AR-PACKAGE>
2134    <AR-PACKAGE><SHORT-NAME>ContentPkg2</SHORT-NAME>
2135      <ELEMENTS>
2136        <ECU-INSTANCE><SHORT-NAME>Ecu</SHORT-NAME></ECU-INSTANCE>
2137      </ELEMENTS>
2138    </AR-PACKAGE>
2139  </AR-PACKAGES>
2140</AUTOSAR>"#.as_bytes();
2141        let model = AutosarModel::new();
2142        let result = model.load_buffer(FILEBUF1, "test", true);
2143        assert!(result.is_ok());
2144
2145        let el_system = model.get_element_by_path("/BasesPkg/SubPackage/System").unwrap();
2146        let el_fibex_elements = el_system.get_sub_element(ElementName::FibexElements).unwrap();
2147        let el_fibex_element_ref = el_fibex_elements
2148            .get_sub_element_at(0)
2149            .and_then(|ferc| ferc.get_sub_element(ElementName::FibexElementRef))
2150            .unwrap();
2151
2152        // check that we can resolve the reference across multiple levels of reference bases
2153        let el_ecu = el_fibex_element_ref.get_reference_target().unwrap();
2154        assert_eq!(el_ecu.element_name(), ElementName::EcuInstance);
2155
2156        // check that the reference origins are correct
2157        let origins = model.get_references_to(&el_ecu.path().unwrap());
2158        assert_eq!(origins.len(), 1);
2159        let origin = origins[0].upgrade().unwrap();
2160        assert_eq!(origin.element_name(), ElementName::FibexElementRef);
2161
2162        let origins2 = model.get_references_to("/ContentPkg2/Ecu");
2163        assert_eq!(origins2.len(), 1);
2164    }
2165}