autosar_data/
autosarmodel.rs

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