Skip to main content

autosar_data/
arxmlfile.rs

1use std::hash::Hash;
2
3use crate::*;
4
5impl ArxmlFile {
6    pub(crate) fn new<P: AsRef<Path>>(filename: P, version: AutosarVersion, model: &AutosarModel) -> Self {
7        ArxmlFileRaw {
8            version,
9            model: model.downgrade(),
10            filename: filename.as_ref().to_path_buf(),
11            xml_standalone: None,
12        }
13        .wrap()
14    }
15
16    /// Get the filename of this `ArxmlFile`
17    ///
18    /// # Example
19    ///
20    /// ```
21    /// # use autosar_data::*;
22    /// # let model = AutosarModel::new();
23    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
24    /// println!("filename is : {}", file.filename().display());
25    /// ```
26    #[must_use]
27    pub fn filename(&self) -> PathBuf {
28        self.0.read().filename.clone()
29    }
30
31    /// Get the [`AutosarVersion`] of the file
32    ///
33    /// # Example
34    ///
35    /// ```
36    /// # use autosar_data::*;
37    /// # let model = AutosarModel::new();
38    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
39    /// let version = file.version();
40    /// ```
41    #[must_use]
42    pub fn version(&self) -> AutosarVersion {
43        self.0.read().version
44    }
45
46    /// Set the [`AutosarVersion`] of the file
47    ///
48    /// The compatibility of the data in the file with the new version will be checked before setting the version.
49    /// The compatibility check can also be performed manually using the function `check_version_compatibility()`.
50    ///
51    /// If the data is compatible, then the version is set, otherwise an error is raised.
52    ///
53    /// # Example
54    ///
55    /// ```
56    /// # use autosar_data::*;
57    /// # let model = AutosarModel::new();
58    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
59    /// file.set_version(AutosarVersion::Autosar_00050);
60    /// ```
61    ///
62    /// # Errors
63    ///
64    ///  - [`AutosarDataError::VersionIncompatibleData`] the existing data is not compatible with the new version
65    ///
66    pub fn set_version(&self, new_ver: AutosarVersion) -> Result<(), AutosarDataError> {
67        let (compat_errors, _) = self.check_version_compatibility(new_ver);
68        if compat_errors.is_empty() {
69            let mut file = self.0.write();
70            file.version = new_ver;
71            Ok(())
72        } else {
73            Err(AutosarDataError::VersionIncompatibleData { version: new_ver })
74        }
75    }
76
77    /// Check if the elements and attributes in this file are compatible with some `target_version`
78    ///
79    /// All elements and their attributes will be evaluated against the target version according to the specification.
80    /// The output is a list of incompatible elements
81    ///
82    /// # Example
83    ///
84    /// ```
85    /// # use autosar_data::*;
86    /// # let model = AutosarModel::new();
87    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
88    /// let (error_list, compat_mask) = file.check_version_compatibility(AutosarVersion::Autosar_00050);
89    /// ```
90    #[must_use]
91    pub fn check_version_compatibility(&self, target_version: AutosarVersion) -> (Vec<CompatibilityError>, u32) {
92        if let Ok(model) = self.model() {
93            model
94                .root_element()
95                .check_version_compatibility(&self.downgrade(), target_version)
96        } else {
97            (Vec::new(), 0)
98        }
99    }
100
101    /// Set the filename of this arxml filename
102    ///
103    /// This will not rename any existing file on disk, but the new filename will be used when writing the data.
104    ///
105    /// # Example
106    ///
107    /// ```
108    /// # use std::path::Path;
109    /// # use autosar_data::*;
110    /// # let model = AutosarModel::new();
111    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
112    /// file.set_filename("foo.arxml");
113    /// // or
114    /// file.set_filename(&Path::new("bar.arxml"));
115    /// ```
116    pub fn set_filename<P: AsRef<Path>>(&self, new_filename: P) -> Result<(), AutosarDataError> {
117        let new_filename = new_filename.as_ref().to_path_buf();
118        if self
119            .model()?
120            .files()
121            .map(|f| (f.clone(), f.filename()))
122            .any(|(file, filename)| file != *self && filename == new_filename)
123        {
124            Err(AutosarDataError::DuplicateFilenameError {
125                verb: "set_filename",
126                filename: new_filename,
127            })
128        } else {
129            self.0.write().filename = new_filename;
130            Ok(())
131        }
132    }
133
134    /// Get a reference to the [`AutosarModel`] object that contains this file
135    ///
136    /// # Example
137    ///
138    /// ```
139    /// # use autosar_data::*;
140    /// # fn main() -> Result<(), AutosarDataError> {
141    /// let model = AutosarModel::new();
142    /// let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
143    /// let m2 = file.model()?;
144    /// assert_eq!(model, m2);
145    /// # Ok(())
146    /// # }
147    /// ```
148    ///
149    /// # Errors
150    ///
151    /// [`AutosarDataError::ItemDeleted`]: The model is no longer valid
152    ///
153    pub fn model(&self) -> Result<AutosarModel, AutosarDataError> {
154        let locked_file = self.0.write();
155        // This reference must always be valid, so it is an error if upgrade() fails
156        locked_file.model.upgrade().ok_or(AutosarDataError::ItemDeleted)
157    }
158
159    /// Create a depth-first search iterator over all [Element]s in this file
160    ///
161    /// In a multi-file model it will not return any elements from other files.
162    ///
163    /// # Example
164    ///
165    /// ```
166    /// # use autosar_data::*;
167    /// # let model = AutosarModel::new();
168    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
169    /// for (depth, elem) in file.elements_dfs() {
170    ///     // ...
171    /// }
172    /// ```
173    #[must_use]
174    pub fn elements_dfs(&self) -> ArxmlFileElementsDfsIterator {
175        ArxmlFileElementsDfsIterator::new(self, 0)
176    }
177
178    /// Create a depth first iterator over all [Element]s in this file, up to a maximum depth
179    ///
180    /// In a multi-file model it will not return any elements from other files.
181    ///
182    /// # Example
183    ///
184    /// ```
185    /// # use autosar_data::*;
186    /// # let model = AutosarModel::new();
187    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
188    /// # let element = model.root_element();
189    /// # element.create_sub_element(ElementName::ArPackages).unwrap();
190    /// # let sub_elem = element.get_sub_element(ElementName::ArPackages).unwrap();
191    /// # sub_elem.create_named_sub_element(ElementName::ArPackage, "test2").unwrap();
192    /// for (depth, elem) in file.elements_dfs_with_max_depth(1) {
193    ///     assert!(depth <= 1);
194    ///     // ...
195    /// }
196    /// ```
197    #[must_use]
198    pub fn elements_dfs_with_max_depth(&self, max_depth: usize) -> ArxmlFileElementsDfsIterator {
199        ArxmlFileElementsDfsIterator::new(self, max_depth)
200    }
201
202    /// Serialize the content of the file to a String
203    ///
204    /// # Example
205    ///
206    /// ```
207    /// # use autosar_data::*;
208    /// # let model = AutosarModel::new();
209    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
210    /// let text = file.serialize();
211    /// ```
212    ///
213    /// # Errors
214    ///
215    /// [`AutosarDataError::ItemDeleted`]: The model is no longer valid
216    /// [`AutosarDataError::EmptyFile`]: The file is empty and cannot be serialized
217    pub fn serialize(&self) -> Result<String, AutosarDataError> {
218        let model = self.model()?;
219        if !model.root_element().file_membership()?.1.contains(&self.downgrade()) {
220            return Err(AutosarDataError::EmptyFile);
221        }
222
223        let mut outstring = String::with_capacity(1024 * 1024);
224
225        match self.xml_standalone() {
226            Some(true) => outstring.push_str("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>"),
227            Some(false) => outstring.push_str("<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"no\"?>"),
228            None => outstring.push_str("<?xml version=\"1.0\" encoding=\"utf-8\"?>"),
229        }
230        let file_version = self.0.read().version;
231        model.root_element().0.read().serialize_internal(
232            &mut outstring,
233            0,
234            false,
235            &Some(self.downgrade()),
236            Some(file_version),
237        );
238
239        Ok(outstring)
240    }
241
242    /// Return the standalone attribute from the xml header
243    ///
244    /// Some tools set headers that include the standalone attribute.
245    /// This attribute appears to be meaningless for arxml files.
246    ///
247    /// It is preserved nonetheless and can be retrieved with this function.
248    ///
249    /// # Example
250    ///
251    /// ```
252    /// # use autosar_data::*;
253    /// let model = AutosarModel::new();
254    /// let file_text = r#"<?xml version="1.0" encoding="utf-8" standalone="no"?>
255    /// <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">
256    /// </AUTOSAR>"#.as_bytes();
257    /// let (file, _warnings) = model.load_buffer(file_text, "filename.arxml", true).unwrap();
258    /// assert_eq!(file.xml_standalone(), Some(false));
259    /// ```
260    #[must_use]
261    pub fn xml_standalone(&self) -> Option<bool> {
262        self.0.read().xml_standalone
263    }
264
265    /// Create a weak reference to this `ArxmlFile`
266    ///
267    /// A weak reference can be stored without preventing the file from being deallocated.
268    /// The weak reference has to be upgraded in order to be used, which can fail if the file no longer exists.
269    ///
270    /// See the documentation for [Arc]
271    ///
272    /// # Example
273    ///
274    /// ```
275    /// # use autosar_data::*;
276    /// # let model = AutosarModel::new();
277    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
278    /// let weak_file = file.downgrade();
279    /// ```
280    #[must_use]
281    pub fn downgrade(&self) -> WeakArxmlFile {
282        WeakArxmlFile(Arc::downgrade(&self.0))
283    }
284}
285
286impl ArxmlFileRaw {
287    pub(crate) fn wrap(self) -> ArxmlFile {
288        ArxmlFile(Arc::new(RwLock::new(self)))
289    }
290}
291
292impl PartialEq for ArxmlFile {
293    fn eq(&self, other: &Self) -> bool {
294        Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
295    }
296}
297
298impl Eq for ArxmlFile {}
299
300impl Hash for ArxmlFile {
301    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
302        state.write_usize(Arc::as_ptr(&self.0) as usize);
303    }
304}
305
306impl std::fmt::Debug for ArxmlFile {
307    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
308        let self_locked = self.0.read();
309        f.debug_struct("ArxmlFile")
310            .field("filename", &self_locked.filename)
311            .field("version", &self_locked.version)
312            .field("model", &self_locked.model)
313            .field("xml_standalone", &self_locked.xml_standalone)
314            .finish()
315    }
316}
317
318impl WeakArxmlFile {
319    /// try to get a strong reference to the [`ArxmlFile`]
320    ///
321    /// This succeeds if the `ArxmlFile` still has any other strong reference to it, otherwise None is returned
322    pub fn upgrade(&self) -> Option<ArxmlFile> {
323        Weak::upgrade(&self.0).map(ArxmlFile)
324    }
325}
326
327impl PartialEq for WeakArxmlFile {
328    fn eq(&self, other: &Self) -> bool {
329        Weak::as_ptr(&self.0) == Weak::as_ptr(&other.0)
330    }
331}
332
333impl Eq for WeakArxmlFile {}
334
335impl Hash for WeakArxmlFile {
336    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
337        state.write_usize(Weak::as_ptr(&self.0) as usize);
338    }
339}
340
341impl std::fmt::Debug for WeakArxmlFile {
342    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
343        if let Some(arxmlfile) = self.upgrade() {
344            f.write_fmt(format_args!("ArxmlFile:WeakRef ({})", arxmlfile.filename().display()))
345        } else {
346            f.write_fmt(format_args!("ArxmlFile:WeakRef {:p} (invalid)", Weak::as_ptr(&self.0)))
347        }
348    }
349}
350
351#[cfg(test)]
352mod test {
353    use super::*;
354
355    #[test]
356    fn create() {
357        let model = AutosarModel::new();
358        let result = model.create_file("test", AutosarVersion::Autosar_4_0_1);
359        assert!(result.is_ok());
360    }
361
362    #[test]
363    fn filename() {
364        let model = AutosarModel::new();
365        let result = model.create_file("test", AutosarVersion::Autosar_4_0_1);
366        let file = result.unwrap();
367        let filename = PathBuf::from("newname.arxml");
368        file.set_filename(filename.clone()).unwrap();
369        assert_eq!(file.filename(), filename);
370    }
371
372    #[test]
373    fn version() {
374        let model: AutosarModel = AutosarModel::new();
375        let file = model.create_file("test", AutosarVersion::Autosar_00051).unwrap();
376
377        let el_elements = model
378            .root_element()
379            .create_sub_element(ElementName::ArPackages)
380            .and_then(|arpkgs| arpkgs.create_named_sub_element(ElementName::ArPackage, "Pkg"))
381            .and_then(|arpkg| arpkg.create_sub_element(ElementName::Elements))
382            .unwrap();
383        let incompatible_elem = el_elements
384            .create_named_sub_element(ElementName::AdaptiveApplicationSwComponentType, "incompatible")
385            .unwrap();
386
387        let result = file.set_version(AutosarVersion::Autosar_4_0_1);
388        assert!(result.is_err());
389
390        el_elements.remove_sub_element(incompatible_elem).unwrap();
391
392        file.set_version(AutosarVersion::Autosar_4_0_1).unwrap();
393        assert_eq!(file.version(), AutosarVersion::Autosar_4_0_1);
394    }
395
396    #[test]
397    fn references() {
398        let model = AutosarModel::new();
399        let result = model.create_file("test", AutosarVersion::Autosar_4_0_1);
400        let file = result.unwrap();
401        let weak_file = file.downgrade();
402        let file2 = weak_file.upgrade().unwrap();
403        assert_eq!(Arc::strong_count(&file.0), 3); // 3 references are: AutosarModel, file, file2
404        assert_eq!(file, file2);
405    }
406
407    #[test]
408    fn standalone() {
409        let model = AutosarModel::new();
410        let file_text = r#"<?xml version="1.0" encoding="utf-8" standalone="no"?>
411            <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">
412            </AUTOSAR>"#.as_bytes();
413        let (file, _warnings) = model.load_buffer(file_text, "filename.arxml", true).unwrap();
414        assert_eq!(file.xml_standalone(), Some(false));
415    }
416
417    #[test]
418    fn serialize() {
419        let model = AutosarModel::new();
420        let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
421        assert_eq!(file.model().unwrap(), model);
422        assert_eq!(model.root_element().element_name(), ElementName::Autosar);
423        assert_eq!(file.version(), AutosarVersion::Autosar_00050);
424        let text = file.serialize().unwrap();
425        assert_eq!(
426            text,
427            r#"<?xml version="1.0" encoding="utf-8"?>
428<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"/>"#
429        );
430        file.0.write().xml_standalone = Some(false);
431        let text = file.serialize().unwrap();
432        assert_eq!(
433            text,
434            r#"<?xml version="1.0" encoding="utf-8" standalone="no"?>
435<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"/>"#
436        );
437        file.0.write().xml_standalone = Some(true);
438        let text = file.serialize().unwrap();
439        assert_eq!(
440            text,
441            r#"<?xml version="1.0" encoding="utf-8" standalone="yes"?>
442<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"/>"#
443        );
444    }
445
446    #[test]
447    fn elements_dfs_iterator() {
448        const FILEBUF_1: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
449        <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">
450        <AR-PACKAGES>
451          <AR-PACKAGE>
452            <SHORT-NAME>Pkg</SHORT-NAME>
453            <ELEMENTS>
454              <SYSTEM><SHORT-NAME>System</SHORT-NAME></SYSTEM>
455            </ELEMENTS>
456          </AR-PACKAGE>
457        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
458        const FILEBUF_2: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
459        <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">
460        <AR-PACKAGES>
461          <AR-PACKAGE>
462            <SHORT-NAME>Pkg2</SHORT-NAME>
463            <ELEMENTS>
464            <APPLICATION-PRIMITIVE-DATA-TYPE><SHORT-NAME>DataType</SHORT-NAME></APPLICATION-PRIMITIVE-DATA-TYPE>
465            </ELEMENTS>
466          </AR-PACKAGE>
467        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
468
469        let model = AutosarModel::new();
470        let (file, _) = model.load_buffer(FILEBUF_1, "file1.arxml", false).unwrap();
471        let proj_elem_count = model.elements_dfs().count();
472        let file_elem_count = file.elements_dfs().count();
473        assert_eq!(proj_elem_count, file_elem_count);
474        model.load_buffer(FILEBUF_2, "file2.arxml", false).unwrap();
475        let proj_elem_count_2 = model.elements_dfs().count();
476        let file_elem_count_2 = file.elements_dfs().count();
477        assert!(proj_elem_count < proj_elem_count_2);
478        assert_eq!(file_elem_count, file_elem_count_2);
479    }
480
481    #[test]
482    fn elements_dfs_with_max_depth() {
483        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
484        <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">
485        <AR-PACKAGES>
486          <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
487            <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
488              <SHORT-NAME>BswModuleValues</SHORT-NAME>
489              <PARAMETER-VALUES>
490                <ECUC-NUMERICAL-PARAM-VALUE>
491                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
492                </ECUC-NUMERICAL-PARAM-VALUE>
493                <ECUC-NUMERICAL-PARAM-VALUE>
494                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
495                </ECUC-NUMERICAL-PARAM-VALUE>
496                <ECUC-NUMERICAL-PARAM-VALUE>
497                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_C</DEFINITION-REF>
498                </ECUC-NUMERICAL-PARAM-VALUE>
499              </PARAMETER-VALUES>
500            </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
501          </ELEMENTS></AR-PACKAGE>
502          <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
503          <AR-PACKAGE><SHORT-NAME>Pkg_C</SHORT-NAME></AR-PACKAGE>
504        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
505        let model = AutosarModel::new();
506        let (file, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
507        let all_count = file.elements_dfs().count();
508        let lvl2_count = file.elements_dfs_with_max_depth(2).count();
509        assert!(all_count > lvl2_count);
510        for elem in file.elements_dfs_with_max_depth(2) {
511            assert!(elem.0 <= 2);
512        }
513    }
514
515    #[test]
516    fn multiple_files_1() {
517        // two files are created, both contain AUTOSAR. Then a child element of AUTOSAR is created, which is present in both files
518        let model = AutosarModel::new();
519        let file_a = model.create_file("a", AutosarVersion::LATEST).unwrap();
520        let file_b = model.create_file("b", AutosarVersion::LATEST).unwrap();
521        let el_a_packages = model
522            .root_element()
523            .create_sub_element(ElementName::ArPackages)
524            .unwrap();
525        // el_a_packages is part of both file_a and file_b, because both files automatically contain the
526        // root AUTOSAR element, and AR_PACKAGES inherits this
527        let (_, fs) = el_a_packages.file_membership().unwrap();
528        assert!(fs.contains(&file_a.downgrade()));
529        assert!(fs.contains(&file_b.downgrade()));
530    }
531
532    #[test]
533    fn multiple_files_2() {
534        // one file is created, which contains AUTOSAR. Then a child element is created, which is automatically part of this file.
535        // then a second file is created, but the element is NOT part of the new file
536        let model = AutosarModel::new();
537        let file_a = model.create_file("a", AutosarVersion::LATEST).unwrap();
538        let el_a_packages = model
539            .root_element()
540            .create_sub_element(ElementName::ArPackages)
541            .unwrap();
542        let file_b = model.create_file("b", AutosarVersion::LATEST).unwrap();
543        // el_a_packages is only part of file_a
544        let (_, fs) = el_a_packages.file_membership().unwrap();
545        assert!(fs.contains(&file_a.downgrade()));
546        assert!(!fs.contains(&file_b.downgrade()));
547    }
548
549    #[test]
550    fn multiple_files_3() {
551        // a file is created with multiple sub elements. A pat of this hierarchy is added to a second file
552        let model = AutosarModel::new();
553        let file_a = model.create_file("a", AutosarVersion::LATEST).unwrap();
554        let el_ar_packages = model
555            .root_element()
556            .create_sub_element(ElementName::ArPackages)
557            .unwrap();
558        let el_pkg1 = el_ar_packages
559            .create_named_sub_element(ElementName::ArPackage, "Pkg1")
560            .unwrap();
561        let el_pkg2 = el_ar_packages
562            .create_named_sub_element(ElementName::ArPackage, "Pkg2")
563            .unwrap();
564        let file_b = model.create_file("b", AutosarVersion::LATEST).unwrap();
565        let (_, fs) = el_pkg1.file_membership().unwrap();
566        assert!(fs.contains(&file_a.downgrade())); // el_pkg1 is part of file_a
567        assert!(!fs.contains(&file_b.downgrade())); // el_pkg1 is not part of file_b
568        let (_, fs) = el_pkg2.file_membership().unwrap();
569        assert!(fs.contains(&file_a.downgrade())); // el_pkg2 is part of file_a
570        assert!(!fs.contains(&file_b.downgrade())); // el_pkg2 is not part of file_b
571
572        // add el_pkg2 to file_b
573        el_pkg2.add_to_file(&file_b).unwrap();
574        let (_, fs) = el_pkg1.file_membership().unwrap();
575        assert!(fs.contains(&file_a.downgrade())); // el_pkg1 is part of file_a
576        assert!(!fs.contains(&file_b.downgrade())); // el_pkg1 is not part of file_b
577        let (_, fs) = el_pkg2.file_membership().unwrap();
578        assert!(fs.contains(&file_a.downgrade())); // el_pkg2 is part of file_a
579        assert!(fs.contains(&file_b.downgrade())); // el_pkg2 is part of file_b
580
581        // el_ar_packages was automatically added to file_b, in order to add el_pkg2
582        let (_, fs) = el_ar_packages.file_membership().unwrap();
583        assert!(fs.contains(&file_a.downgrade())); // el_ar_packages is part of file_a
584        assert!(fs.contains(&file_b.downgrade())); // el_ar_packages is part of file_b
585
586        // remove el_pkg2 from file_a
587        let (_, fs) = el_pkg2.file_membership().unwrap();
588        assert!(fs.contains(&file_a.downgrade())); // el_pkg2 is part of file_a
589        assert!(fs.contains(&file_b.downgrade())); // el_pkg2 is part of file_b
590        el_pkg2.remove_from_file(&file_a).unwrap();
591        let (_, fs) = el_pkg2.file_membership().unwrap();
592        assert!(!fs.contains(&file_a.downgrade())); // el_pkg2 is part of file_a
593        assert!(fs.contains(&file_b.downgrade())); // el_pkg2 is part of file_b
594
595        // add_to_file / remove_from_file cannot be called on all elements
596        let el_elements = el_pkg1.create_sub_element(ElementName::Elements).unwrap();
597        let result = el_elements.add_to_file(&file_a);
598        assert!(matches!(result, Err(AutosarDataError::FilesetModificationForbidden)));
599        let result: Result<(), AutosarDataError> = el_elements.remove_from_file(&file_a);
600        assert!(matches!(result, Err(AutosarDataError::FilesetModificationForbidden)));
601
602        // serializing a single file
603        let text_before = file_a.serialize().unwrap();
604        model.remove_file(&file_b);
605        assert!(model.get_element_by_path("/Pkg2").is_none());
606        let text_after = file_a.serialize().unwrap();
607        assert_eq!(text_before, text_after);
608    }
609
610    #[test]
611    fn traits() {
612        let model = AutosarModel::new();
613        let file = model.create_file("filename", AutosarVersion::LATEST).unwrap();
614        let weak_file = file.downgrade();
615        let file_cloned = file.clone();
616        assert_eq!(file, file_cloned);
617        assert_eq!(format!("{file:#?}"), format!("{file_cloned:#?}"));
618        #[allow(clippy::mutable_key_type)]
619        let mut hashset = HashSet::<ArxmlFile>::new();
620        hashset.insert(file);
621        let inserted = hashset.insert(file_cloned);
622        assert!(!inserted);
623
624        let weak_file_cloned = weak_file.clone();
625        assert_eq!(weak_file, weak_file_cloned);
626        assert_eq!(format!("{weak_file:#?}"), format!("{weak_file_cloned:#?}"));
627        let mut hashset = HashSet::<WeakArxmlFile>::new();
628        hashset.insert(weak_file);
629        let inserted = hashset.insert(weak_file_cloned);
630        assert!(!inserted);
631    }
632
633    #[test]
634    fn debug_format_of_dangling_weak_file() {
635        let model = AutosarModel::new();
636        let file = model.create_file("filename", AutosarVersion::LATEST).unwrap();
637        let weak_file = file.downgrade();
638        model.remove_file(&file);
639        drop(file);
640        // the weak reference can no longer be upgraded, so Debug prints the pointer instead of the filename
641        assert!(format!("{weak_file:#?}").contains("(invalid)"));
642    }
643
644    #[test]
645    fn set_filename_rejects_duplicates() {
646        let model = AutosarModel::new();
647        let file_a = model.create_file("a.arxml", AutosarVersion::LATEST).unwrap();
648        let file_b = model.create_file("b.arxml", AutosarVersion::LATEST).unwrap();
649
650        // renaming a file to the name of a different file in the same model is not allowed
651        assert!(matches!(
652            file_b.set_filename("a.arxml"),
653            Err(AutosarDataError::DuplicateFilenameError {
654                verb: "set_filename",
655                ..
656            })
657        ));
658        assert_eq!(file_b.filename(), PathBuf::from("b.arxml"));
659
660        // setting the name a file already has is not a conflict with itself
661        file_a.set_filename("a.arxml").unwrap();
662        assert_eq!(file_a.filename(), PathBuf::from("a.arxml"));
663    }
664
665    #[test]
666    fn serialize_empty_file() {
667        let model = AutosarModel::new();
668        let file_a = model.create_file("a.arxml", AutosarVersion::LATEST).unwrap();
669        let file_b = model.create_file("b.arxml", AutosarVersion::LATEST).unwrap();
670
671        // remove the root element from file_b: now file_b has no content at all
672        model.root_element().remove_from_file(&file_b).unwrap();
673
674        assert!(matches!(file_b.serialize(), Err(AutosarDataError::EmptyFile)));
675        assert!(file_a.serialize().is_ok());
676    }
677
678    #[test]
679    fn operations_on_a_file_whose_model_is_gone() {
680        let file = {
681            let model = AutosarModel::new();
682            model.create_file("test", AutosarVersion::LATEST).unwrap()
683        };
684        // the model has been dropped, so the file can't reach it any more
685        assert!(matches!(file.model(), Err(AutosarDataError::ItemDeleted)));
686        let (errors, mask) = file.check_version_compatibility(AutosarVersion::LATEST);
687        assert!(errors.is_empty());
688        assert_eq!(mask, 0);
689    }
690}