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 #[must_use]
27 pub fn filename(&self) -> PathBuf {
28 self.0.read().filename.clone()
29 }
30
31 #[must_use]
42 pub fn version(&self) -> AutosarVersion {
43 self.0.read().version
44 }
45
46 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 #[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 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 pub fn model(&self) -> Result<AutosarModel, AutosarDataError> {
154 let locked_file = self.0.write();
155 locked_file.model.upgrade().ok_or(AutosarDataError::ItemDeleted)
157 }
158
159 #[must_use]
174 pub fn elements_dfs(&self) -> ArxmlFileElementsDfsIterator {
175 ArxmlFileElementsDfsIterator::new(self, 0)
176 }
177
178 #[must_use]
198 pub fn elements_dfs_with_max_depth(&self, max_depth: usize) -> ArxmlFileElementsDfsIterator {
199 ArxmlFileElementsDfsIterator::new(self, max_depth)
200 }
201
202 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 #[must_use]
261 pub fn xml_standalone(&self) -> Option<bool> {
262 self.0.read().xml_standalone
263 }
264
265 #[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 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); 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 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 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 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 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 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())); assert!(!fs.contains(&file_b.downgrade())); let (_, fs) = el_pkg2.file_membership().unwrap();
569 assert!(fs.contains(&file_a.downgrade())); assert!(!fs.contains(&file_b.downgrade())); el_pkg2.add_to_file(&file_b).unwrap();
574 let (_, fs) = el_pkg1.file_membership().unwrap();
575 assert!(fs.contains(&file_a.downgrade())); assert!(!fs.contains(&file_b.downgrade())); let (_, fs) = el_pkg2.file_membership().unwrap();
578 assert!(fs.contains(&file_a.downgrade())); assert!(fs.contains(&file_b.downgrade())); let (_, fs) = el_ar_packages.file_membership().unwrap();
583 assert!(fs.contains(&file_a.downgrade())); assert!(fs.contains(&file_b.downgrade())); let (_, fs) = el_pkg2.file_membership().unwrap();
588 assert!(fs.contains(&file_a.downgrade())); assert!(fs.contains(&file_b.downgrade())); el_pkg2.remove_from_file(&file_a).unwrap();
591 let (_, fs) = el_pkg2.file_membership().unwrap();
592 assert!(!fs.contains(&file_a.downgrade())); assert!(fs.contains(&file_b.downgrade())); 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 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 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 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 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 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 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}