Skip to main content

autosar_data/
element.rs

1use std::cmp::Ordering;
2use std::hash::Hash;
3use std::str::FromStr;
4
5use super::*;
6
7static LOCK_CONTENTION_TIMEOUT: std::time::Duration = std::time::Duration::from_millis(10);
8
9impl Element {
10    /// Get the parent element of the current element
11    ///
12    /// Returns None if the current element is the root, or if it has been deleted from the element hierarchy
13    ///
14    /// # Example
15    ///
16    /// ```
17    /// # use autosar_data::*;
18    /// # fn main() -> Result<(), AutosarDataError> {
19    /// # let model = AutosarModel::new();
20    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
21    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
22    /// if let Some(parent) = element.parent()? {
23    ///     // ...
24    /// }
25    /// # Ok(())
26    /// # }
27    /// ```
28    ///
29    /// # Errors
30    ///
31    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
32    pub fn parent(&self) -> Result<Option<Element>, AutosarDataError> {
33        self.0.read().parent()
34    }
35
36    /// Get the next named parent (or grandparent, etc) of the current element
37    ///
38    /// This function steps through the hierarchy until an identifiable element is found.
39    /// It never returns the current element, even if the current element is identifiable.
40    ///
41    /// The function returns a suitable element if one is found, or None if the root is reached.
42    ///
43    /// # Example
44    ///
45    /// ```
46    /// # use autosar_data::*;
47    /// # fn main() -> Result<(), AutosarDataError> {
48    /// # let model = AutosarModel::new();
49    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050)?;
50    /// # let el_arpackage = model.root_element().create_sub_element(ElementName::ArPackages)?.create_named_sub_element(ElementName::ArPackage, "Pkg")?;
51    /// let el_elements = el_arpackage.create_sub_element(ElementName::Elements)?;
52    /// let el_system = el_elements.create_named_sub_element(ElementName::System, "Sys")?;
53    /// let named_parent = el_elements.named_parent()?.unwrap();
54    /// let named_parent2 = el_system.named_parent()?.unwrap();
55    /// assert_eq!(named_parent, el_arpackage);
56    /// assert_eq!(named_parent2, el_arpackage);
57    /// # Ok(())
58    /// # }
59    /// ```
60    ///
61    /// # Errors
62    ///
63    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
64    pub fn named_parent(&self) -> Result<Option<Element>, AutosarDataError> {
65        let mut cur_elem_opt = self.parent()?;
66        while let Some(parent) = cur_elem_opt {
67            if parent.is_identifiable() {
68                return Ok(Some(parent));
69            }
70            cur_elem_opt = parent.parent()?;
71        }
72
73        Ok(None)
74    }
75
76    pub(crate) fn set_parent(&self, new_parent: ElementOrModel) {
77        self.0.write().set_parent(new_parent);
78    }
79
80    /// point this element and all of its sub elements at the given model
81    ///
82    /// Every element caches the model it belongs to next to its parent reference, so a subtree that
83    /// was built outside of a model, by `deep_copy` or in a different model or by a move between
84    /// models, has to be updated when it is attached. The element itself already has the right
85    /// parent reference at this point, so setting it again is harmless.
86    pub(crate) fn set_model_recursive(&self, model: &WeakAutosarModel) {
87        for (_, element) in self.elements_dfs() {
88            if let ElementOrModel::Element(_, element_model) = &mut element.0.write().parent {
89                *element_model = model.clone();
90            }
91        }
92    }
93
94    /// detach this element and all of its sub elements from their model
95    ///
96    /// The elements keep their content, so handles to any of them remain usable, but they no
97    /// longer have a parent or a model.
98    pub(crate) fn detach_recursive(&self) {
99        for (_, element) in self.elements_dfs() {
100            element.0.write().parent = ElementOrModel::None;
101        }
102    }
103
104    /// Get the [`ElementName`] of the element
105    ///
106    /// # Example
107    ///
108    /// ```
109    /// # use autosar_data::*;
110    /// # let model = AutosarModel::new();
111    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
112    /// let element = model.root_element();
113    /// let element_name = element.element_name();
114    /// assert_eq!(element_name, ElementName::Autosar);
115    /// ```
116    #[must_use]
117    pub fn element_name(&self) -> ElementName {
118        self.0.read().elemname
119    }
120
121    /// Get the [`ElementType`] of the element
122    ///
123    /// The `ElementType` is needed in order to call methods from the autosar-data-specification crate
124    ///
125    /// # Example
126    ///
127    /// ```
128    /// # use autosar_data::*;
129    /// # let model = AutosarModel::new();
130    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
131    /// # let element = model.root_element();
132    /// let element_type = element.element_type();
133    /// ```
134    #[must_use]
135    pub fn element_type(&self) -> ElementType {
136        self.0.read().elemtype
137    }
138
139    /// Get the name of an identifiable element
140    ///
141    /// An identifiable element has a `<SHORT-NAME>` sub element and can be referenced using an autosar path.
142    ///
143    /// If the element is not identifiable, this function returns None
144    ///
145    /// # Example
146    ///
147    /// ```
148    /// # use autosar_data::*;
149    /// # let model = AutosarModel::new();
150    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
151    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
152    /// if let Some(item_name) = element.item_name() {
153    ///     // ...
154    /// }
155    /// ```
156    #[must_use]
157    pub fn item_name(&self) -> Option<String> {
158        self.0.read().item_name()
159    }
160
161    /// Set the item name of this element
162    ///
163    /// This operation will update all references pointing to the element or its sub-elements so that they remain valid.
164    ///
165    /// # Example
166    ///
167    /// ```
168    /// # use autosar_data::*;
169    /// # let model = AutosarModel::new();
170    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
171    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
172    /// element.set_item_name("NewName");
173    /// ```
174    ///
175    /// # Note
176    ///
177    /// In order to rename an element *without* updating any references, do this instead:
178    /// ```
179    /// # use autosar_data::*;
180    /// # let model = AutosarModel::new();
181    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
182    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
183    /// if let Some(short_name) = element.get_sub_element(ElementName::ShortName) {
184    ///     short_name.set_character_data("the_new_name");
185    /// }
186    /// ```
187    ///
188    /// # Errors
189    ///
190    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
191    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
192    ///    The operation was aborted to avoid a deadlock, but can be retried.
193    ///  - [`AutosarDataError::ItemNameRequired`] this function was called for an element which is not identifiable
194    ///
195    pub fn set_item_name(&self, new_name: &str) -> Result<(), AutosarDataError> {
196        // a new name is required
197        if new_name.is_empty() {
198            return Err(AutosarDataError::ItemNameRequired {
199                element: self.element_name(),
200            });
201        }
202        let (model, version) = self.model_and_version()?;
203        self.0.write().set_item_name(new_name, &model, version)
204    }
205
206    /// Returns true if the element is identifiable
207    ///
208    /// In order to be identifiable, the specification must require a SHORT-NAME
209    /// sub-element and the SHORT-NAME must actually be present.
210    ///
211    /// # Example
212    /// ```
213    /// # use autosar_data::*;
214    /// # let model = AutosarModel::new();
215    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
216    /// # let element = model.root_element();
217    /// if element.is_identifiable() {
218    ///     // ...
219    /// }
220    /// ```
221    #[must_use]
222    pub fn is_identifiable(&self) -> bool {
223        self.0.read().is_identifiable()
224    }
225
226    /// Returns true if the element should contain a reference to another element
227    ///
228    /// The function does not check if the reference is valid
229    ///
230    /// # Example
231    ///
232    /// ```
233    /// # use autosar_data::*;
234    /// # let model = AutosarModel::new();
235    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
236    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
237    /// if element.is_reference() {
238    ///     // ex: element.set_reference_target(...)
239    /// }
240    /// ```
241    #[must_use]
242    pub fn is_reference(&self) -> bool {
243        self.elemtype().is_ref()
244    }
245
246    /// Get the Autosar path of an identifiable element
247    ///
248    /// # Example
249    ///
250    /// ```
251    /// # use autosar_data::*;
252    /// # fn main() -> Result<(), AutosarDataError> {
253    /// # let model = AutosarModel::new();
254    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
255    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
256    /// let path = element.path()?;
257    /// # Ok(())
258    /// # }
259    /// ```
260    ///
261    /// # Errors
262    ///
263    ///  - [`AutosarDataError::ItemDeleted`]: Th ecurrent element is in the deleted state and will be freed once the last reference is dropped
264    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
265    ///    The operation was aborted to avoid a deadlock, but can be retried.
266    ///  - [`AutosarDataError::ElementNotIdentifiable`]: The current element is not identifiable, so it has no Autosar path
267    ///
268    pub fn path(&self) -> Result<String, AutosarDataError> {
269        // path() is frequently called on parent elements while a child lock is held,
270        // so a blocking read() here could deadlock
271        self.0
272            .try_read_for(LOCK_CONTENTION_TIMEOUT)
273            .ok_or(AutosarDataError::ParentElementLocked)?
274            .path()
275    }
276
277    /// Get the package element containing the current element
278    ///
279    /// It never returns the current element, even if the current element is a package, but always
280    /// goes up the hierarchy to find the nearest parent package.
281    ///
282    /// Returns
283    ///   Ok(Some(Element)) if a parent package is found
284    ///   Ok(None) at the top level.
285    ///   Err if the action cannot be completed
286    ///
287    /// # Example
288    ///
289    /// ```
290    /// # use autosar_data::*;
291    /// # fn main() -> Result<(), AutosarDataError> {
292    /// # let model = AutosarModel::new();
293    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
294    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)?.create_named_sub_element(ElementName::ArPackage, "Pkg")?;
295    /// let package = element.package()?;
296    /// # Ok(())
297    /// # }
298    /// ```
299    ///
300    /// # Errors
301    ///
302    /// - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
303    /// - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
304    ///   The operation was aborted to avoid a deadlock, but can be retried.
305    pub fn package(&self) -> Result<Option<Element>, AutosarDataError> {
306        let mut cur_elem = self.clone();
307        loop {
308            let parent = {
309                let element = cur_elem
310                    .0
311                    .try_read_for(LOCK_CONTENTION_TIMEOUT)
312                    .ok_or(AutosarDataError::ParentElementLocked)?;
313                match &element.parent {
314                    ElementOrModel::Element(weak_parent, _) => {
315                        let parent = weak_parent.upgrade().ok_or(AutosarDataError::ItemDeleted)?;
316                        // avoid deadlocking if the parent is locked, because we're already holding a child lock here
317                        let parent_name = {
318                            let parent_lock = parent
319                                .0
320                                .try_read_for(LOCK_CONTENTION_TIMEOUT)
321                                .ok_or(AutosarDataError::ParentElementLocked)?;
322                            parent_lock.element_name()
323                        };
324
325                        if parent_name == ElementName::ArPackage {
326                            return Ok(Some(parent));
327                        }
328                        parent
329                    }
330                    ElementOrModel::Model(_) => {
331                        return Ok(None);
332                    }
333                    ElementOrModel::None => return Err(AutosarDataError::ItemDeleted),
334                }
335            };
336            cur_elem = parent;
337        }
338    }
339
340    /// Get a reference to the [`AutosarModel`] containing the current element
341    ///
342    /// # Example
343    ///
344    /// ```
345    /// # use autosar_data::*;
346    /// # fn main() -> Result<(), AutosarDataError> {
347    /// # let model = AutosarModel::new();
348    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
349    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages).and_then(|pkgs| pkgs.create_named_sub_element(ElementName::ArPackage, "name")).unwrap();
350    /// let file = element.model()?;
351    /// # Ok(())
352    /// # }
353    /// ```
354    ///
355    /// # Errors
356    ///
357    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
358    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
359    ///    The operation was aborted to avoid a deadlock, but can be retried.
360    ///
361    pub fn model(&self) -> Result<AutosarModel, AutosarDataError> {
362        self.0
363            .try_read_for(LOCK_CONTENTION_TIMEOUT)
364            .ok_or(AutosarDataError::ParentElementLocked)?
365            .model()
366    }
367
368    /// Get the [`ContentType`] of the current element
369    ///
370    /// # Example
371    ///
372    /// ```
373    /// # use autosar_data::*;
374    /// # let model = AutosarModel::new();
375    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
376    /// # let element = model.root_element();
377    /// if element.content_type() == ContentType::CharacterData {
378    ///     // ...
379    /// }
380    /// ```
381    #[must_use]
382    pub fn content_type(&self) -> ContentType {
383        self.elemtype().content_mode().into()
384    }
385
386    /// Create a sub element at a suitable insertion position
387    ///
388    /// The given `ElementName` must be allowed on a sub element in this element, taking into account any sub elements that may already exist.
389    /// It is not possible to create named sub elements with this function; use `create_named_sub_element`() for that instead.
390    ///
391    /// # Example
392    ///
393    /// ```
394    /// # use autosar_data::*;
395    /// # fn main() -> Result<(), AutosarDataError> {
396    /// # let model = AutosarModel::new();
397    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
398    /// let element = model.root_element().create_sub_element(ElementName::ArPackages)?;
399    /// # Ok(())
400    /// # }
401    /// ```
402    ///
403    /// # Errors
404    ///
405    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
406    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
407    ///    The operation was aborted to avoid a deadlock, but can be retried.
408    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
409    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
410    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
411    ///  - [`AutosarDataError::ItemNameRequired`]: The sub element requires an item name, so you must use `create_named_sub_element`().
412    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
413    pub fn create_sub_element(&self, element_name: ElementName) -> Result<Element, AutosarDataError> {
414        let version = self.min_version()?;
415        self.0
416            .try_write()
417            .ok_or(AutosarDataError::ParentElementLocked)?
418            .create_sub_element(self.downgrade(), element_name, version)
419    }
420
421    /// Create a sub element at the specified insertion position
422    ///
423    /// The given `ElementName` must be allowed on a sub element in this element, taking into account any sub elements that may already exist.
424    /// It is not possible to create named sub elements with this function; use `create_named_sub_element_at`() for that instead.
425    ///
426    /// The specified insertion position will be compared to the range of valid insertion positions; if it falls outside that range then the function fails.
427    ///
428    /// # Example
429    ///
430    /// ```
431    /// # use autosar_data::*;
432    /// # fn main() -> Result<(), AutosarDataError> {
433    /// # let model = AutosarModel::new();
434    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
435    /// let element = model.root_element().create_sub_element_at(ElementName::ArPackages, 0)?;
436    /// # Ok(())
437    /// # }
438    /// ```
439    ///
440    /// # Errors
441    ///
442    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
443    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
444    ///    The operation was aborted to avoid a deadlock, but can be retried.
445    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
446    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
447    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
448    ///  - [`AutosarDataError::ItemNameRequired`]: The sub element requires an item name, so you must use `create_named_sub_element_at`().
449    ///  - [`AutosarDataError::InvalidPosition`]: This sub element cannot be created at the requested position
450    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
451    pub fn create_sub_element_at(
452        &self,
453        element_name: ElementName,
454        position: usize,
455    ) -> Result<Element, AutosarDataError> {
456        let version = self.min_version()?;
457        self.0
458            .write()
459            .create_sub_element_at(self.downgrade(), element_name, position, version)
460    }
461
462    /// Create a named/identifiable sub element at a suitable insertion position
463    ///
464    /// The given `ElementName` must be allowed on a sub element in this element, taking into account any sub elements that may already exist.
465    ///
466    /// This method can only be used to create identifiable sub elements.
467    ///
468    /// # Example
469    ///
470    /// ```
471    /// # use autosar_data::*;
472    /// # fn main() -> Result<(), AutosarDataError> {
473    /// # let model = AutosarModel::new();
474    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
475    /// let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
476    /// let element = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")?;
477    /// # Ok(())
478    /// # }
479    /// ```
480    ///
481    /// # Errors
482    ///
483    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
484    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
485    ///    The operation was aborted to avoid a deadlock, but can be retried.
486    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
487    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
488    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
489    ///  - [`AutosarDataError::ElementNotIdentifiable`]: The sub element does not have an item name, so you must use `create_sub_element`() instead.
490    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
491    pub fn create_named_sub_element(
492        &self,
493        element_name: ElementName,
494        item_name: &str,
495    ) -> Result<Element, AutosarDataError> {
496        let (model, version) = self.model_and_version()?;
497        self.0
498            .write()
499            .create_named_sub_element(self.downgrade(), element_name, item_name, &model, version)
500    }
501
502    /// Create a named/identifiable sub element at the specified insertion position
503    ///
504    /// The given `ElementName` must be allowed on a sub element in this element, taking into account any sub elements that may already exist.
505    /// The specified insertion position will be compared to the range of valid insertion positions; if it falls outside that range then the function fails.
506    ///
507    /// This method can only be used to create identifiable sub elements.
508    ///
509    /// # Example
510    ///
511    /// ```
512    /// # use autosar_data::*;
513    /// # fn main() -> Result<(), AutosarDataError> {
514    /// # let model = AutosarModel::new();
515    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
516    /// let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
517    /// let element = pkgs_element.create_named_sub_element_at(ElementName::ArPackage, "Package", 0)?;
518    /// # Ok(())
519    /// # }
520    /// ```
521    ///
522    /// # Errors
523    ///
524    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
525    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
526    ///    The operation was aborted to avoid a deadlock, but can be retried.
527    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
528    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
529    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
530    ///  - [`AutosarDataError::ElementNotIdentifiable`]: The sub element does not have an item name, so you must use `create_sub_element`() instead.
531    ///  - [`AutosarDataError::InvalidPosition`]: This sub element cannot be created at the requested position.
532    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
533    pub fn create_named_sub_element_at(
534        &self,
535        element_name: ElementName,
536        item_name: &str,
537        position: usize,
538    ) -> Result<Element, AutosarDataError> {
539        let (model, version) = self.model_and_version()?;
540        self.0
541            .write()
542            .create_named_sub_element_at(self.downgrade(), element_name, item_name, position, &model, version)
543    }
544
545    /// Create a deep copy of the given element and insert it as a sub-element
546    ///
547    /// The other element must be a permissible sub-element in this element and not conflict with any existing sub element.
548    /// The other element can originate from any loaded [`AutosarModel`], it does not have to originate from the same model or file as the current element.
549    ///
550    /// The [`AutosarVersion`] of the other element might differ from the version of the current file;
551    /// in this case a partial copy will be performed that omits all incompatible elements.
552    ///
553    /// If the copied element is identifiable, then the item name might be extended with a numerical suffix, if one is required in order to make the name unique.
554    /// For example: An identifiable element "Foo" already exists at the same path; the copied identifiable element will be renamed to "`Foo_1`".
555    ///
556    /// If the copied element or the hierarchy of elements under it contain any references, then these will need to be adjusted manually after copying.
557    ///
558    /// # Example
559    ///
560    /// ```
561    /// # use autosar_data::*;
562    /// # fn main() -> Result<(), AutosarDataError> {
563    /// # let model = AutosarModel::new();
564    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
565    /// # let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
566    /// # let base = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")
567    /// #    .and_then(|p| p.create_sub_element(ElementName::Elements))?;
568    /// # base.create_named_sub_element(ElementName::System, "Path")?;
569    /// let other_element = model.get_element_by_path("/Package/Path").unwrap();
570    /// let element = base.create_copied_sub_element(&other_element)?;
571    /// # Ok(())
572    /// # }
573    /// ```
574    ///
575    /// # Errors
576    ///
577    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
578    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
579    ///    The operation was aborted to avoid a deadlock, but can be retried.
580    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
581    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
582    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
583    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
584    pub fn create_copied_sub_element(&self, other: &Element) -> Result<Element, AutosarDataError> {
585        if self == other {
586            // trying to copy self into self never makes sense, and would deadlock
587            return Err(AutosarDataError::InvalidSubElement {
588                parent: self.element_name(),
589                element: self.element_name(),
590            });
591        }
592        let (model, version) = self.model_and_version()?;
593        let copy = self
594            .0
595            .write()
596            .create_copied_sub_element(self.downgrade(), other, &model, version);
597        // the copy may contain relative references and REFERENCE-BASE declarations, neither of which
598        // could be resolved while the element was being copied
599        model.resolve_relative_references();
600        copy
601    }
602
603    /// Create a deep copy of the given element and insert it as a sub-element at the given position
604    ///
605    /// The other element must be a permissible sub-element in this element and not conflict with any existing sub element.
606    /// The other element can originate from any loaded [`AutosarModel`], it does not have to originate from the same model or file as the current element.
607    ///
608    /// The [`AutosarVersion`] of the other element might differ from the version of the current file;
609    /// in this case a partial copy will be performed that omits all incompatible elements.
610    ///
611    /// If the copied element is identifiable, then the item name might be extended with a numerical suffix, if one is required in order to make the name unique.
612    /// For example: An identifiable element "Foo" already exists at the same path; the copied identifiable element will be renamed to "`Foo_1`".
613    ///
614    /// If the copied element or the hierarchy of elements under it contain any references, then these will need to be adjusted manually after copying.
615    ///
616    /// # Example
617    ///
618    /// ```
619    /// # use autosar_data::*;
620    /// # fn main() -> Result<(), AutosarDataError> {
621    /// # let model = AutosarModel::new();
622    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
623    /// # let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
624    /// # let base = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")
625    /// #    .and_then(|pkg| pkg.create_sub_element(ElementName::Elements))?;
626    /// # base.create_named_sub_element(ElementName::System, "Path")?;
627    /// let other_element = model.get_element_by_path("/Package/Path").unwrap();
628    /// let element = base.create_copied_sub_element_at(&other_element, 0)?;
629    /// # Ok(())
630    /// # }
631    /// ```
632    ///
633    /// # Errors
634    ///
635    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
636    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
637    ///    The operation was aborted to avoid a deadlock, but can be retried.
638    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
639    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
640    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
641    ///  - [`AutosarDataError::InvalidPosition`]: This sub element cannot be created at the requested position.
642    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
643    pub fn create_copied_sub_element_at(&self, other: &Element, position: usize) -> Result<Element, AutosarDataError> {
644        if self == other {
645            // trying to copy self into self never makes sense, and would deadlock
646            return Err(AutosarDataError::InvalidSubElement {
647                parent: self.element_name(),
648                element: self.element_name(),
649            });
650        }
651        let (model, version) = self.model_and_version()?;
652        let copy = self
653            .0
654            .write()
655            .create_copied_sub_element_at(self.downgrade(), other, position, &model, version);
656        // the copy may contain relative references and REFERENCE-BASE declarations, neither of which
657        // could be resolved while the element was being copied
658        model.resolve_relative_references();
659        copy
660    }
661
662    /// Take an `element` from it's current location and place it in this element as a sub element
663    ///
664    /// The moved element can be taken from anywhere - even from a different arxml document that is not part of the same `AutosarModel`
665    ///
666    /// Restrictions:
667    /// 1) The element must have a compatible element type. If it could not have been created here, then it can't be moved either.
668    /// 2) The origin document of the element must have exactly the same `AutosarVersion` as the destination.
669    ///
670    /// # Example
671    ///
672    /// ```
673    /// # use autosar_data::*;
674    /// # fn main() -> Result<(), AutosarDataError> {
675    /// # let model = AutosarModel::new();
676    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
677    /// # let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
678    /// # let base = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")
679    /// #    .and_then(|pkg| pkg.create_sub_element(ElementName::Elements))?;
680    /// # base.create_named_sub_element(ElementName::System, "Path")?;
681    /// let other_element = model.get_element_by_path("/Package/Path").unwrap();
682    /// let element = base.move_element_here(&other_element)?;
683    /// # Ok(())
684    /// # }
685    /// ```
686    ///
687    /// # Errors
688    ///
689    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
690    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
691    ///    The operation was aborted to avoid a deadlock, but can be retried.
692    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
693    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
694    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
695    ///  - [`AutosarDataError::VersionMismatch`]: The Autosar versions of the source and destination are different
696    ///  - [`AutosarDataError::ForbiddenMoveToSubElement`]: The destination is a sub element of the source. Moving here is not possible
697    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
698    pub fn move_element_here(&self, move_element: &Element) -> Result<Element, AutosarDataError> {
699        if self == move_element {
700            // trying to move self into self never makes sense, and would deadlock
701            return Err(AutosarDataError::ForbiddenMoveToSubElement);
702        }
703        let model_src = move_element.model()?;
704        let model = self.model()?;
705        let version_src = move_element.min_version()?;
706        let version = self.min_version()?;
707        if version != version_src {
708            return Err(AutosarDataError::VersionMismatch {
709                version_cur: version,
710                version_new: version_src,
711            });
712        }
713        let moved = self
714            .0
715            .write()
716            .move_element_here(self.downgrade(), move_element, &model, &model_src, version);
717        // the moved elements may contain relative references which are now in the scope of different
718        // reference bases, and they may declare reference bases themselves
719        model_src.resolve_relative_references();
720        model.resolve_relative_references();
721        moved
722    }
723
724    /// Take an `element` from it's current location and place it at the given position in this element as a sub element
725    ///
726    /// The moved element can be taken from anywhere - even from a different arxml document that is not part of the same `AutosarModel`
727    ///
728    /// Restrictions:
729    /// 1) The element must have a compatible element type. If it could not have been created here, then it can't be moved either.
730    /// 2) The origin document of the element must have exactly the same `AutosarVersion` as the destination.
731    ///
732    /// # Example
733    ///
734    /// ```
735    /// # use autosar_data::*;
736    /// # fn main() -> Result<(), AutosarDataError> {
737    /// # let model = AutosarModel::new();
738    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
739    /// # let pkgs_element = model.root_element().create_sub_element(ElementName::ArPackages)?;
740    /// # let base = pkgs_element.create_named_sub_element(ElementName::ArPackage, "Package")
741    /// #    .and_then(|p| p.create_sub_element(ElementName::Elements))?;
742    /// # base.create_named_sub_element(ElementName::System, "Path")?;
743    /// let other_element = model.get_element_by_path("/Package/Path").unwrap();
744    /// let element = base.move_element_here_at(&other_element, 0)?;
745    /// # Ok(())
746    /// # }
747    /// ```
748    ///
749    /// # Errors
750    ///
751    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
752    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
753    ///    The operation was aborted to avoid a deadlock, but can be retried.
754    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
755    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
756    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
757    ///  - [`AutosarDataError::VersionMismatch`]: The Autosar versions of the source and destination are different
758    ///  - [`AutosarDataError::ForbiddenMoveToSubElement`]: The destination is a sub element of the source. Moving here is not possible
759    ///  - [`AutosarDataError::InvalidPosition`]: This sub element cannot be created at the requested position.
760    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
761    pub fn move_element_here_at(&self, move_element: &Element, position: usize) -> Result<Element, AutosarDataError> {
762        if self == move_element {
763            // trying to move self into self never makes sense, and would deadlock
764            return Err(AutosarDataError::ForbiddenMoveToSubElement);
765        }
766        let model_src = move_element.model()?;
767        let model = self.model()?;
768        let version_src = move_element.min_version()?;
769        let version = self.min_version()?;
770        if version != version_src {
771            return Err(AutosarDataError::VersionMismatch {
772                version_cur: version,
773                version_new: version_src,
774            });
775        }
776        let moved =
777            self.0
778                .write()
779                .move_element_here_at(self.downgrade(), move_element, position, &model, &model_src, version);
780        // the moved elements may contain relative references which are now in the scope of different
781        // reference bases, and they may declare reference bases themselves
782        model_src.resolve_relative_references();
783        model.resolve_relative_references();
784        moved
785    }
786
787    /// Remove the sub element `sub_element`
788    ///
789    /// The `sub_element` will be unlinked from the hierarchy of elements.
790    /// All of the sub-sub-elements nested under the removed element will also be recursively removed.
791    ///
792    /// Since all elements are reference counted, they might not be deallocated immediately, however they do become invalid and unusable immediately.
793    ///
794    /// # Example
795    ///
796    /// ```
797    /// # use autosar_data::*;
798    /// # fn main() -> Result<(), AutosarDataError> {
799    /// # let model = AutosarModel::new();
800    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
801    /// let packages = model.root_element().create_sub_element(ElementName::ArPackages)?;
802    /// model.root_element().remove_sub_element(packages)?;
803    /// # Ok(())
804    /// # }
805    /// ```
806    ///
807    /// # Errors
808    ///
809    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
810    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
811    ///    The operation was aborted to avoid a deadlock, but can be retried.
812    ///  - [`AutosarDataError::ElementNotFound`]: The sub element was not found in this element
813    ///  - [`AutosarDataError::ShortNameRemovalForbidden`]: It is not permitted to remove the SHORT-NAME of identifiable elements since this would result in invalid data
814    pub fn remove_sub_element(&self, sub_element: Element) -> Result<(), AutosarDataError> {
815        if *self == sub_element {
816            // an element is never a sub element of itself; without this check the operation would deadlock
817            return Err(AutosarDataError::ElementNotFound {
818                target: self.element_name(),
819                parent: self.element_name(),
820            });
821        }
822        let model = self.model()?;
823        let result = self.0.write().remove_sub_element(sub_element, &model);
824        // removing a REFERENCE-BASE changes what the relative references in its scope resolve to
825        model.resolve_relative_references();
826        result
827    }
828
829    /// Remove a sub element identified by an ElementName
830    ///
831    /// If multiple sub elements with the same ElementName exist, only the first one will be removed.
832    ///
833    /// A sub element with the given ElementName will be unlinked from the hierarchy of elements.
834    /// All of the sub-sub-elements nested under the removed element will also be recursively removed.
835    ///
836    /// This is a convenience function that is equivalent to calling `get_sub_element()` followed by `remove_sub_element()`.
837    ///
838    /// # Example
839    ///
840    /// ```
841    /// # use autosar_data::*;
842    /// # fn main() -> Result<(), AutosarDataError> {
843    /// # let model = AutosarModel::new();
844    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
845    /// let packages = model.root_element().create_sub_element(ElementName::ArPackages)?;
846    /// model.root_element().remove_sub_element_kind(ElementName::ArPackages)?;
847    /// # Ok(())
848    /// # }
849    /// ```
850    ///
851    /// # Errors
852    ///
853    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
854    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
855    ///    The operation was aborted to avoid a deadlock, but can be retried.
856    ///  - [`AutosarDataError::ElementNotFound`]: The sub element was not found in this element
857    ///  - [`AutosarDataError::ShortNameRemovalForbidden`]: It is not permitted to remove the SHORT-NAME of identifiable elements since this would result in invalid data
858    pub fn remove_sub_element_kind(&self, element_name: ElementName) -> Result<(), AutosarDataError> {
859        let Some(sub_element) = self.get_sub_element(element_name) else {
860            return Err(AutosarDataError::ElementNotFound {
861                target: element_name,
862                parent: self.element_name(),
863            });
864        };
865        self.remove_sub_element(sub_element)
866    }
867
868    /// Set the reference target for the element to target
869    ///
870    /// When the reference is updated, the DEST attribute is also updated to match the referenced element.
871    /// The current element must be a reference element, otherwise the function fails.
872    ///
873    /// # Example
874    ///
875    /// ```
876    /// # use autosar_data::*;
877    /// # fn main() -> Result<(), AutosarDataError> {
878    /// # let model = AutosarModel::new();
879    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
880    /// # let elements = model.root_element().create_sub_element(ElementName::ArPackages)
881    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
882    /// #   .and_then(|e| e.create_sub_element(ElementName::Elements))?;
883    /// # let ref_element = elements.create_named_sub_element(ElementName::System, "System")
884    /// #   .and_then(|e| e.create_sub_element(ElementName::FibexElements))
885    /// #   .and_then(|e| e.create_sub_element(ElementName::FibexElementRefConditional))
886    /// #   .and_then(|e| e.create_sub_element(ElementName::FibexElementRef))?;
887    /// let cluster_element = elements.create_named_sub_element(ElementName::CanCluster, "Cluster")?;
888    /// ref_element.set_reference_target(&cluster_element)?;
889    /// # Ok(())
890    /// # }
891    /// ```
892    ///
893    /// # Errors
894    ///
895    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
896    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
897    ///    The operation was aborted to avoid a deadlock, but can be retried.
898    ///  - [`AutosarDataError::NotReferenceElement`]: The current element is not a reference, so it is not possible to set a reference target
899    ///  - [`AutosarDataError::InvalidReference`]: The target element is not a valid reference target for this reference
900    ///  - [`AutosarDataError::ElementNotIdentifiable`]: The target element is not identifiable, so it cannot be referenced by an Autosar path
901    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
902    pub fn set_reference_target(&self, target: &Element) -> Result<(), AutosarDataError> {
903        let result = self.set_reference_target_internal(target, None);
904        // the reference is registered while this element is locked, so it can only be resolved here
905        if let Ok(model) = self.model() {
906            model.resolve_relative_references();
907        }
908        result
909    }
910
911    /// Set the reference target using a relative path and explicit BASE label
912    ///
913    /// This method behaves like [`Self::set_reference_target`], but it writes
914    /// a relative reference path and sets the BASE attribute to `base_label`.
915    ///
916    /// # Errors
917    ///
918    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
919    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
920    ///    The operation was aborted to avoid a deadlock, but can be retried.
921    ///  - [`AutosarDataError::NotReferenceElement`]: The current element is not a reference, so it is not possible to set a reference target
922    ///  - [`AutosarDataError::InvalidReference`]: The target element is not a valid reference target for this reference
923    ///  - [`AutosarDataError::InvalidReferenceBase`]: The target element uses an invalid base label
924    ///  - [`AutosarDataError::ElementNotIdentifiable`]: The target element is not identifiable, so it cannot be referenced by an Autosar path
925    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
926    pub fn set_relative_reference_target(&self, target: &Element, base_label: &str) -> Result<(), AutosarDataError> {
927        let result = self.set_reference_target_internal(target, Some(base_label));
928        // the reference is registered while this element is locked, so it can only be resolved here
929        if let Ok(model) = self.model() {
930            model.resolve_relative_references();
931        }
932        result
933    }
934
935    /// Resolve the reference base named `label`, for a relative reference located in this element
936    ///
937    /// A REFERENCE-BASE is declared by an AR-PACKAGE, and it is in scope for that package as well as for
938    /// everything nested inside it. The applicable declaration is therefore found by walking up the
939    /// ancestor packages of the reference and taking the first one which declares `label`.
940    ///
941    /// The PACKAGE-REF of a REFERENCE-BASE may carry a BASE attribute of its own, i.e. it can be
942    /// relative to another reference base. It is then resolved in the scope of the package which
943    /// declares it, and the two paths are joined. Invalid data can make this chain cyclic, so the
944    /// declarations which have already been visited are tracked in order to avoid looping forever.
945    ///
946    /// The result is `None` if the label is not in scope, if this element is not inside a package, or if
947    /// an element lock could not be acquired: this reads the element tree while the caller may hold the
948    /// model lock, so every element lock is taken with a timeout.
949    pub(crate) fn resolve_reference_base(&self, label: &str) -> Option<String> {
950        let mut search_package = self.package().ok().flatten()?;
951        let mut label = label.to_owned();
952        // the path components contributed by the chained reference bases, innermost first
953        let mut relative_parts: Vec<String> = Vec::new();
954        let mut visited: Vec<(Element, String)> = Vec::new();
955
956        loop {
957            let (declaring_package, package_ref, package_ref_base) =
958                Self::find_reference_base_declaration(&search_package, &label)?;
959            if visited.contains(&(declaring_package.clone(), label.clone())) {
960                // a cycle in the chain of reference bases - the base cannot be resolved
961                return None;
962            }
963            visited.push((declaring_package.clone(), label));
964
965            let Some(outer_label) = package_ref_base else {
966                // the end of the chain: this PACKAGE-REF contains an absolute path
967                let mut resolved = package_ref;
968                for part in relative_parts.iter().rev() {
969                    resolved.push('/');
970                    resolved.push_str(part);
971                }
972                return Some(resolved);
973            };
974            relative_parts.push(package_ref);
975            // the BASE attribute of the PACKAGE-REF is resolved in the scope of the package which
976            // declares this reference base
977            search_package = declaring_package;
978            label = outer_label;
979        }
980    }
981
982    /// Resolve this relative reference to the absolute path of the element it refers to
983    ///
984    /// This is the definition of what a relative reference means, and therefore of the key it is
985    /// registered under in the model. The result is `None` if this is not a usable relative reference:
986    /// it has no character data or no BASE attribute, it is not inside a package, or its reference base
987    /// is not in scope.
988    pub(crate) fn resolve_relative_target(&self) -> Option<String> {
989        let reference = self.character_data()?.string_value()?;
990        let base_label = self.attribute_value(AttributeName::Base)?.string_value()?;
991        let base_path = self.resolve_reference_base(&base_label)?;
992        Some(format!("{base_path}/{reference}"))
993    }
994
995    /// Find the nearest declaration of the reference base `label`: `package` itself is searched first,
996    /// followed by its ancestor packages
997    ///
998    /// The result is the declaring package, the character data of its PACKAGE-REF, and the BASE
999    /// attribute of that PACKAGE-REF.
1000    fn find_reference_base_declaration(package: &Element, label: &str) -> Option<(Element, String, Option<String>)> {
1001        let mut current_package = Some(package.clone());
1002        while let Some(package) = current_package {
1003            if let Some(reference_bases) = package.try_get_sub_element(ElementName::ReferenceBases) {
1004                let declarations: Vec<Element> = reference_bases
1005                    .0
1006                    .try_read_for(LOCK_CONTENTION_TIMEOUT)?
1007                    .content
1008                    .iter()
1009                    .filter_map(|item| match item {
1010                        ElementContent::Element(sub_element) => Some(sub_element.clone()),
1011                        ElementContent::CharacterData(_) => None,
1012                    })
1013                    .collect();
1014                for declaration in declarations {
1015                    if let Some((declared_label, package_ref, package_ref_base)) = declaration
1016                        .0
1017                        .try_read_for(LOCK_CONTENTION_TIMEOUT)?
1018                        .reference_base_declaration()
1019                        && declared_label == label
1020                    {
1021                        return Some((package, package_ref, package_ref_base));
1022                    }
1023                }
1024            }
1025            current_package = package.package().ok().flatten();
1026        }
1027        None
1028    }
1029
1030    /// Get the first sub element with the given name, acquiring every element lock with a timeout
1031    ///
1032    /// Unlike [`Self::get_sub_element`] this can be used while the model lock is held.
1033    fn try_get_sub_element(&self, name: ElementName) -> Option<Element> {
1034        let locked_element = self.0.try_read_for(LOCK_CONTENTION_TIMEOUT)?;
1035        for item in &locked_element.content {
1036            if let ElementContent::Element(sub_element) = item
1037                && sub_element.0.try_read_for(LOCK_CONTENTION_TIMEOUT)?.elemname == name
1038            {
1039                return Some(sub_element.clone());
1040            }
1041        }
1042        None
1043    }
1044
1045    fn set_reference_target_internal(
1046        &self,
1047        target: &Element,
1048        base_label: Option<&str>,
1049    ) -> Result<(), AutosarDataError> {
1050        // the current element must be a reference
1051        if !self.is_reference() {
1052            return Err(AutosarDataError::NotReferenceElement);
1053        }
1054
1055        // the target element must be identifiable, i.e. it has an autosar path
1056        let new_ref = target.path()?;
1057        // it must be possible to use the name of the referenced element name as an enum item in the dest attribute of the reference
1058        let Some(enum_item) = EnumItem::from_str(target.element_name().to_str())
1059            .ok()
1060            .or(self.element_type().reference_dest_value(&target.element_type()))
1061        else {
1062            return Err(AutosarDataError::InvalidReference);
1063        };
1064
1065        let model = self.model()?;
1066        // build a target string, which is either the absolute path or the relative path depending on whether a base label was provided
1067        let target_string = if let Some(base_label) = base_label {
1068            // a relative reference can only be resolved if the reference element is inside a package;
1069            // this is not always the case, e.g. references inside AUTOSAR > ADMIN-DATA are outside any package
1070            let base_path = self
1071                .resolve_reference_base(base_label)
1072                .ok_or(AutosarDataError::InvalidReferenceBase)?;
1073            let mut trimmed_target_path = new_ref
1074                .strip_prefix(&base_path)
1075                .ok_or(AutosarDataError::InvalidReferenceBase)?;
1076            if let Some(no_leading_slash) = trimmed_target_path.strip_prefix('/') {
1077                trimmed_target_path = no_leading_slash;
1078            }
1079            trimmed_target_path.to_string()
1080        } else {
1081            new_ref
1082        };
1083
1084        let version = self.min_version()?;
1085
1086        let mut element = self.0.write();
1087        // set the DEST attribute first - this could fail if the target element has the wrong type
1088        if element
1089            .set_attribute_internal(AttributeName::Dest, CharacterData::Enum(enum_item), version)
1090            .is_err()
1091        {
1092            return Err(AutosarDataError::InvalidReference);
1093        }
1094
1095        let opt_old_ref = element.character_data().and_then(|cdata| cdata.string_value());
1096
1097        // if this reference previously referenced some other element, update
1098        if let Some(old_ref) = opt_old_ref {
1099            model.fix_reference_origins(&old_ref, &target_string, base_label, self.downgrade());
1100        } else {
1101            // else initialise the new reference
1102            model.add_reference_origin(&target_string, base_label, self.downgrade());
1103        }
1104
1105        // do the update - Note: The update is infallible in practice, since we've constructed a valid target string
1106        element.set_character_data(CharacterData::String(target_string), version)?;
1107        if let Some(base_label) = base_label {
1108            element.set_attribute_internal(
1109                AttributeName::Base,
1110                CharacterData::String(base_label.to_string()),
1111                version,
1112            )?;
1113        } else {
1114            // ensure BASE does not remain when retargeting from a relative reference to an absolute reference
1115            element.remove_attribute(AttributeName::Base);
1116        }
1117
1118        Ok(())
1119    }
1120
1121    /// Get the referenced element
1122    ///
1123    /// This function will get the reference string from the character data of the element
1124    /// as well as the destination type from the DEST attribute. Then a lookup of the Autosar
1125    /// path is performed, and if an element is found at that path, then the type of the
1126    /// element is compared to the expected type.
1127    ///
1128    /// The element is returned if it exists and its type is correct.
1129    ///
1130    /// # Example
1131    ///
1132    /// ```
1133    /// # use autosar_data::*;
1134    /// # fn main() -> Result<(), AutosarDataError> {
1135    /// # let model = AutosarModel::new();
1136    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1137    /// # let elements = model.root_element().create_sub_element(ElementName::ArPackages)
1138    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
1139    /// #   .and_then(|e| e.create_sub_element(ElementName::Elements))?;
1140    /// # let ref_element = elements.create_named_sub_element(ElementName::System, "System")
1141    /// #   .and_then(|e| e.create_sub_element(ElementName::FibexElements))
1142    /// #   .and_then(|e| e.create_sub_element(ElementName::FibexElementRefConditional))
1143    /// #   .and_then(|e| e.create_sub_element(ElementName::FibexElementRef))?;
1144    /// let cluster_element = elements.create_named_sub_element(ElementName::CanCluster, "Cluster")?;
1145    /// ref_element.set_reference_target(&cluster_element)?;
1146    /// let ref_target = ref_element.get_reference_target()?;
1147    /// assert_eq!(cluster_element, ref_target);
1148    /// # Ok(())
1149    /// # }
1150    /// ```
1151    ///
1152    /// # Errors
1153    ///
1154    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1155    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1156    ///    The operation was aborted to avoid a deadlock, but can be retried.
1157    ///  - [`AutosarDataError::NotReferenceElement`]: The current element is not a reference, so it is not possible to get the reference target
1158    ///  - [`AutosarDataError::InvalidReference`]: The reference is invalid; there is no element with the referenced Autosar path
1159    pub fn get_reference_target(&self) -> Result<Element, AutosarDataError> {
1160        if self.is_reference() {
1161            if let Some(CharacterData::String(reference)) = self.character_data() {
1162                let model = self.model()?;
1163
1164                let full_path = if self.attribute_value(AttributeName::Base).is_some() {
1165                    // The target path of a relative reference is already known: it is the key that the
1166                    // reference is registered under, so its reference base does not have to be resolved
1167                    // again here. The path is absent while the base is not in scope, which happens e.g.
1168                    // for a reference inside AUTOSAR > ADMIN-DATA, outside of any package.
1169                    model
1170                        .relative_reference_target(&self.downgrade())
1171                        .ok_or(AutosarDataError::InvalidReference)?
1172                } else {
1173                    reference
1174                };
1175                let target_elem = model
1176                    .get_element_by_path(&full_path)
1177                    .ok_or(AutosarDataError::InvalidReference)?;
1178
1179                let dest_value = self
1180                    .attribute_value(AttributeName::Dest)
1181                    .and_then(|cdata| cdata.enum_value())
1182                    .ok_or(AutosarDataError::InvalidReference)?;
1183                if target_elem.element_type().verify_reference_dest(dest_value) {
1184                    Ok(target_elem)
1185                } else {
1186                    Err(AutosarDataError::InvalidReference)
1187                }
1188            } else {
1189                Err(AutosarDataError::InvalidReference)
1190            }
1191        } else {
1192            Err(AutosarDataError::NotReferenceElement)
1193        }
1194    }
1195
1196    /// Set the character data of this element
1197    ///
1198    /// This method only applies to elements which contain character data, i.e. `element.content_type` == `CharacterData` or Mixed.
1199    /// On elements with mixed content this function will replace all current content with the single new `CharacterData` item.
1200    ///
1201    /// # Example
1202    ///
1203    /// ```
1204    /// # use autosar_data::*;
1205    /// # fn main() -> Result<(), AutosarDataError> {
1206    /// # let model = AutosarModel::new();
1207    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1208    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1209    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?
1210    /// #   .get_sub_element(ElementName::ShortName).unwrap();
1211    /// element.set_character_data("value")?;
1212    /// # Ok(())
1213    /// # }
1214    /// ```
1215    ///
1216    /// # Errors
1217    ///
1218    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1219    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1220    ///    The operation was aborted to avoid a deadlock, but can be retried.
1221    ///  - [`AutosarDataError::IncorrectContentType`]: Cannot set character data on an element which does not contain character data
1222    ///  - [`AutosarDataError::InvalidCharacterData`]: The character data is not valid for this element - wrong type, or does not match the
1223    ///    pattern or string restrictions of the element type
1224    pub fn set_character_data<T: Into<CharacterData>>(&self, value: T) -> Result<(), AutosarDataError> {
1225        let chardata: CharacterData = value.into();
1226        self.set_character_data_internal(chardata)
1227    }
1228
1229    // internal function to set the character data - separated out since it doesn't need to be generic
1230    fn set_character_data_internal(&self, mut chardata: CharacterData) -> Result<(), AutosarDataError> {
1231        let elemtype = self.elemtype();
1232        if (elemtype.content_mode() == ContentMode::Characters || elemtype.content_mode() == ContentMode::Mixed)
1233            && let Some(cdata_spec) = elemtype.chardata_spec()
1234        {
1235            let (model, version) = self.model_and_version()?;
1236            let mut compatible_value = CharacterData::check_value(&chardata, cdata_spec, version);
1237            if !compatible_value
1238                && matches!(
1239                    cdata_spec,
1240                    CharacterDataSpec::Pattern { .. } | CharacterDataSpec::String { .. }
1241                )
1242            {
1243                chardata = CharacterData::String(chardata.to_string());
1244                compatible_value = CharacterData::check_value(&chardata, cdata_spec, version);
1245            }
1246            if compatible_value {
1247                // if this is a SHORT-NAME element a whole lot of handling is needed in order to unbreak all the cross references
1248                let mut prev_path = None;
1249                if self.element_name() == ElementName::ShortName {
1250                    // this SHORT-NAME element might be newly created, in which case there is no previous path
1251                    if self.character_data().is_some()
1252                        && let Some(parent) = self.parent()?
1253                    {
1254                        prev_path = Some(parent.path()?);
1255                    }
1256                };
1257
1258                // if this is a reference, then some extra effort is needed there too
1259                let old_refval = if elemtype.is_ref() {
1260                    self.character_data().and_then(|cdata| cdata.string_value())
1261                } else {
1262                    None
1263                };
1264
1265                // update the character data
1266                {
1267                    let mut element = self.0.write();
1268                    element.content.clear();
1269                    element.content.push(ElementContent::CharacterData(chardata));
1270                }
1271
1272                // short-name: make sure the hashmap in the top-level AutosarModel is updated so that this element can still be found
1273                if let Some(prev_path) = prev_path
1274                    && let Some(parent) = self.parent()?
1275                {
1276                    let new_path = parent.path()?;
1277                    model.fix_element_paths(&PathRemap::single(prev_path, new_path));
1278                    // note: set_character_data explicitly does not adapt references to the renamed element, unlike set_item_name
1279                }
1280
1281                // reference: update the references hashmap in the top-level AutosarModel
1282                if elemtype.is_ref()
1283                    && let Some(CharacterData::String(refval)) = self.character_data()
1284                {
1285                    let base = self
1286                        .attribute_value(AttributeName::Base)
1287                        .and_then(|cdata| cdata.string_value());
1288                    if let Some(old_refval) = old_refval {
1289                        model.fix_reference_origins(&old_refval, &refval, base.as_deref(), self.downgrade());
1290                    } else {
1291                        model.add_reference_origin(&refval, base.as_deref(), self.downgrade());
1292                    }
1293                }
1294
1295                // This may have been a reference, or the SHORT-LABEL or PACKAGE-REF of a
1296                // REFERENCE-BASE, which changes what the relative references in its scope resolve to.
1297                model.resolve_relative_references();
1298
1299                Ok(())
1300            } else {
1301                Err(AutosarDataError::InvalidCharacterData {
1302                    element: self.element_name(),
1303                    value: chardata.to_string(),
1304                })
1305            }
1306        } else {
1307            Err(AutosarDataError::IncorrectContentType {
1308                element: self.element_name(),
1309            })
1310        }
1311    }
1312
1313    /// Remove the character data of this element
1314    ///
1315    /// This method only applies to elements which contain character data, i.e. `element.content_type` == `CharacterData`
1316    ///
1317    /// # Example
1318    ///
1319    /// ```
1320    /// # use autosar_data::*;
1321    /// # fn main() -> Result<(), AutosarDataError> {
1322    /// # let model = AutosarModel::new();
1323    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1324    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1325    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
1326    /// #   .and_then(|e| e.create_sub_element(ElementName::Elements))
1327    /// #   .and_then(|e| e.create_named_sub_element(ElementName::System, "System"))
1328    /// #   .and_then(|e| e. create_sub_element(ElementName::PncVectorLength))
1329    /// #   .unwrap();
1330    /// element.remove_character_data()?;
1331    /// # Ok(())
1332    /// # }
1333    /// ```
1334    ///
1335    /// # Errors
1336    ///
1337    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1338    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1339    ///    The operation was aborted to avoid a deadlock, but can be retried. Only relevant when removing references.
1340    ///  - [`AutosarDataError::ShortNameRemovalForbidden`]: Removing the character content of a SHORT-NAME is forbidden
1341    ///  - [`AutosarDataError::IncorrectContentType`]: Cannot set character data on an element whoch does not contain character data
1342    pub fn remove_character_data(&self) -> Result<(), AutosarDataError> {
1343        let elemtype = self.elemtype();
1344        if elemtype.content_mode() == ContentMode::Characters {
1345            if self.element_name() == ElementName::ShortName {
1346                Err(AutosarDataError::ShortNameRemovalForbidden)
1347            } else {
1348                if self.character_data().is_some() {
1349                    if self.is_reference() {
1350                        // the model is required in order to de-register the reference
1351                        let model = self.model()?;
1352                        if let Some(CharacterData::String(reference)) = self.character_data() {
1353                            model.remove_reference_origin(&reference, self.downgrade());
1354                        }
1355                    }
1356                    self.0.write().content.clear();
1357                    // this may have been the SHORT-LABEL or PACKAGE-REF of a REFERENCE-BASE, which the
1358                    // relative references in its scope resolve through
1359                    if let Ok(model) = self.model() {
1360                        model.resolve_relative_references();
1361                    }
1362                }
1363                Ok(())
1364            }
1365        } else {
1366            Err(AutosarDataError::IncorrectContentType {
1367                element: self.element_name(),
1368            })
1369        }
1370    }
1371
1372    /// Insert a character data item into the content of this element
1373    ///
1374    /// This method only applies to elements which contain mixed data, i.e. `element.content_type`() == Mixed.
1375    /// Use `create_sub_element_at` to add an element instead of a character data item
1376    ///
1377    /// # Example
1378    ///
1379    /// ```
1380    /// # use autosar_data::*;
1381    /// # fn main() -> Result<(), AutosarDataError> {
1382    /// # let model = AutosarModel::new();
1383    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1384    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1385    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?;
1386    /// // mixed content elements are primarily used for documentation and description
1387    /// let desc = element.create_sub_element(ElementName::Desc)?;
1388    /// let l2 = desc.create_sub_element(ElementName::L2)?;
1389    /// l2.insert_character_content_item("descriptive text", 0)?;
1390    /// # Ok(())
1391    /// # }
1392    /// ```
1393    ///
1394    /// # Errors
1395    ///
1396    ///  - [`AutosarDataError::IncorrectContentType`] the element `content_type` is not Mixed
1397    ///  - [`AutosarDataError::InvalidPosition`] the position is not valid
1398    pub fn insert_character_content_item(&self, chardata: &str, position: usize) -> Result<(), AutosarDataError> {
1399        let mut element = self.0.write();
1400        if let ContentMode::Mixed = element.elemtype.content_mode() {
1401            if position <= element.content.len() {
1402                element.content.insert(
1403                    position,
1404                    ElementContent::CharacterData(CharacterData::String(chardata.to_owned())),
1405                );
1406                Ok(())
1407            } else {
1408                Err(AutosarDataError::InvalidPosition)
1409            }
1410        } else {
1411            Err(AutosarDataError::IncorrectContentType {
1412                element: element.element_name(),
1413            })
1414        }
1415    }
1416
1417    /// Remove a character data item from the content of this element
1418    ///
1419    /// This method only applies to elements which contain mixed data, i.e. `element.content_type` == Mixed
1420    ///
1421    /// # Example
1422    ///
1423    /// ```
1424    /// # use autosar_data::*;
1425    /// # fn main() -> Result<(), AutosarDataError> {
1426    /// # let model = AutosarModel::new();
1427    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1428    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1429    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
1430    /// #   .and_then(|e| e.create_sub_element(ElementName::Desc))
1431    /// #   .and_then(|e| e.create_sub_element(ElementName::L2))?;
1432    /// element.insert_character_content_item("descriptive text", 0)?;
1433    /// element.remove_character_content_item(0)?;
1434    /// # Ok(())
1435    /// # }
1436    /// ```
1437    ///
1438    /// # Errors
1439    ///
1440    ///  - [`AutosarDataError::IncorrectContentType`] the element `content_type` is not Mixed
1441    ///  - [`AutosarDataError::InvalidPosition`] the position is not valid
1442    pub fn remove_character_content_item(&self, position: usize) -> Result<(), AutosarDataError> {
1443        let mut element = self.0.write();
1444        if let ContentMode::Mixed = element.elemtype.content_mode() {
1445            if position < element.content.len()
1446                && let ElementContent::CharacterData(_) = element.content[position]
1447            {
1448                element.content.remove(position);
1449                return Ok(());
1450            }
1451            Err(AutosarDataError::InvalidPosition)
1452        } else {
1453            Err(AutosarDataError::IncorrectContentType {
1454                element: element.element_name(),
1455            })
1456        }
1457    }
1458
1459    /// returns the number of content items in this element
1460    /// ```
1461    /// # use autosar_data::*;
1462    /// # fn main() -> Result<(), AutosarDataError> {
1463    /// # let model = AutosarModel::new();
1464    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1465    /// # let pkg = model.root_element().create_sub_element(ElementName::ArPackages)
1466    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?;
1467    /// assert_eq!(pkg.content_item_count(), 1);
1468    /// # Ok(())
1469    /// # }
1470    /// ```
1471    #[must_use]
1472    pub fn content_item_count(&self) -> usize {
1473        self.0.read().content.len()
1474    }
1475
1476    /// Get the character content of the element
1477    ///
1478    /// This method only applies to elements which contain character data, i.e. `element.content_type`() == `CharacterData`
1479    ///
1480    /// # Example
1481    ///
1482    /// ```
1483    /// # use autosar_data::*;
1484    /// # fn main() -> Result<(), AutosarDataError> {
1485    /// # let model = AutosarModel::new();
1486    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1487    /// # let element = model.root_element().create_sub_element(ElementName::ArPackages)
1488    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?
1489    /// #   .get_sub_element(ElementName::ShortName).unwrap();
1490    /// match element.character_data() {
1491    ///     Some(CharacterData::String(stringval)) => {},
1492    ///     Some(CharacterData::Enum(enumval)) => {},
1493    ///     Some(CharacterData::UnsignedInteger(intval)) => {},
1494    ///     Some(CharacterData::Float(floatval)) => {},
1495    ///     None => {},
1496    /// }
1497    /// # Ok(())
1498    /// # }
1499    /// ```
1500    #[must_use]
1501    pub fn character_data(&self) -> Option<CharacterData> {
1502        self.0.read().character_data()
1503    }
1504
1505    /// Create an iterator over all of the content of this element
1506    ///
1507    /// The iterator can return both sub elements and character data, wrapped as `ElementContent::Element` and `ElementContent::CharacterData`
1508    ///
1509    /// This method is intended to be used with elements that contain mixed content.
1510    ///
1511    /// # Example
1512    ///
1513    /// ```
1514    /// # use autosar_data::*;
1515    /// # let model = AutosarModel::new();
1516    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1517    /// # let element = model.root_element();
1518    /// for content_item in element.content() {
1519    ///     match content_item {
1520    ///         ElementContent::CharacterData(data) => {},
1521    ///         ElementContent::Element(element) => {},
1522    ///     }
1523    /// }
1524    /// ```
1525    #[must_use]
1526    pub fn content(&self) -> ElementContentIterator {
1527        ElementContentIterator::new(self)
1528    }
1529
1530    /// Create a weak reference to this element
1531    ///
1532    /// A weak reference can be stored without preventing the element from being deallocated.
1533    /// The weak reference has to be upgraded in order to be used, which can fail if the element no longer exists.
1534    ///
1535    /// See the documentation for [Arc]
1536    ///
1537    /// # Example
1538    ///
1539    /// ```
1540    /// # use autosar_data::*;
1541    /// # let model = AutosarModel::new();
1542    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1543    /// # let element = model.root_element();
1544    /// let weak_element = element.downgrade();
1545    /// ```
1546    #[must_use]
1547    pub fn downgrade(&self) -> WeakElement {
1548        WeakElement(Arc::downgrade(&self.0))
1549    }
1550
1551    /// return the position of this element within the parent element
1552    ///
1553    /// None may be returned if the element has been deleted, or for the root element (AUTOSAR) which has no parent.
1554    /// The returned position can be used with `get_sub_element_at()`.
1555    ///
1556    /// # Example
1557    ///
1558    /// ```
1559    /// # use autosar_data::*;
1560    /// # let model = AutosarModel::new();
1561    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1562    /// # let el_ar_packages = model.root_element().create_sub_element(ElementName::ArPackages).unwrap();
1563    /// let el_pkg1 = el_ar_packages.create_named_sub_element(ElementName::ArPackage, "Pkg1").unwrap();
1564    /// let el_pkg2 = el_ar_packages.create_named_sub_element(ElementName::ArPackage, "Pkg2").unwrap();
1565    /// let el_pkg3 = el_ar_packages.create_named_sub_element(ElementName::ArPackage, "Pkg3").unwrap();
1566    /// let position = el_pkg2.position().unwrap();
1567    /// assert_eq!(position, 1);
1568    /// assert_eq!(el_pkg2, el_ar_packages.get_sub_element_at(position).unwrap());
1569    /// ```
1570    #[must_use]
1571    pub fn position(&self) -> Option<usize> {
1572        if let Ok(Some(parent)) = self.parent() {
1573            parent
1574                .0
1575                .read()
1576                .content
1577                .iter()
1578                .position(|ec| matches!(ec, ElementContent::Element(elem) if elem == self))
1579        } else {
1580            None
1581        }
1582    }
1583
1584    /// Create an iterator over all sub elements of this element
1585    ///
1586    /// If the element is modified while the iterator is in use, the iterator can skip sub elements or return duplicates.
1587    ///
1588    /// # Example
1589    ///
1590    /// ```
1591    /// # use autosar_data::*;
1592    /// # let model = AutosarModel::new();
1593    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1594    /// # let element = model.root_element();
1595    /// for sub_element in element.sub_elements() {
1596    ///     // ...
1597    /// }
1598    /// ```
1599    #[must_use]
1600    pub fn sub_elements(&self) -> ElementsIterator {
1601        ElementsIterator::new(self.clone())
1602    }
1603
1604    /// Get the sub element with the given element name
1605    ///
1606    /// Returns None if no such element exists. if there are multiple sub elements with the requested name, then only the first is returned
1607    ///
1608    /// # Example
1609    ///
1610    /// ```
1611    /// # use autosar_data::*;
1612    /// # fn main() -> Result<(), AutosarDataError> {
1613    /// # let model = AutosarModel::new();
1614    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1615    /// # let pkg = model.root_element().create_sub_element(ElementName::ArPackages)
1616    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?;
1617    /// let element = pkg.get_sub_element(ElementName::ShortName).unwrap();
1618    /// assert_eq!(element.element_name(), ElementName::ShortName);
1619    /// # Ok(())
1620    /// # }
1621    /// ```
1622    #[must_use]
1623    pub fn get_sub_element(&self, name: ElementName) -> Option<Element> {
1624        let locked_elem = self.0.read();
1625        for item in &locked_elem.content {
1626            if let ElementContent::Element(subelem) = item
1627                && subelem.element_name() == name
1628            {
1629                return Some(subelem.clone());
1630            }
1631        }
1632        None
1633    }
1634
1635    /// Get the sub element at the given position.
1636    ///
1637    /// Returns None if no such element exists.
1638    ///
1639    /// # Example
1640    ///
1641    /// ```
1642    /// # use autosar_data::*;
1643    /// # fn main() -> Result<(), AutosarDataError> {
1644    /// # let model = AutosarModel::new();
1645    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1646    /// # let pkg = model.root_element().create_sub_element(ElementName::ArPackages)
1647    /// #   .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))?;
1648    /// let element = pkg.get_sub_element_at(0).unwrap();
1649    /// assert_eq!(element.element_name(), ElementName::ShortName);
1650    /// # Ok(())
1651    /// # }
1652    /// ```
1653    #[must_use]
1654    pub fn get_sub_element_at(&self, position: usize) -> Option<Element> {
1655        let locked_elem = self.0.read();
1656        if let Some(ElementContent::Element(subelem)) = locked_elem.content.get(position) {
1657            return Some(subelem.clone());
1658        }
1659        None
1660    }
1661
1662    /// Get or create a sub element
1663    ///
1664    /// This is a shorthand for `get_sub_element` followed by `create_cub_element` if getting an existing element fails.
1665    ///
1666    /// # Example
1667    ///
1668    /// ```
1669    /// # use autosar_data::*;
1670    /// # fn main() -> Result<(), AutosarDataError> {
1671    /// # let model = AutosarModel::new();
1672    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1673    /// let element = model.root_element().get_or_create_sub_element(ElementName::ArPackages)?;
1674    /// let element2 = model.root_element().get_or_create_sub_element(ElementName::ArPackages)?;
1675    /// assert_eq!(element, element2);
1676    /// # Ok(())
1677    /// # }
1678    /// ```
1679    ///
1680    /// # Errors
1681    ///
1682    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1683    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1684    ///    The operation was aborted to avoid a deadlock, but can be retried.
1685    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
1686    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
1687    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
1688    ///  - [`AutosarDataError::ItemNameRequired`]: The sub element requires an item name, so you must use `create_named_sub_element`().
1689    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1690    pub fn get_or_create_sub_element(&self, name: ElementName) -> Result<Element, AutosarDataError> {
1691        let version = self.min_version()?;
1692        let mut locked_elem = self.0.try_write().ok_or(AutosarDataError::ParentElementLocked)?;
1693        for item in &locked_elem.content {
1694            if let ElementContent::Element(subelem) = item
1695                && subelem.element_name() == name
1696            {
1697                return Ok(subelem.clone());
1698            }
1699        }
1700        locked_elem.create_sub_element(self.downgrade(), name, version)
1701    }
1702    /// Get or create a named sub element
1703    ///
1704    /// Checks if a matching subelement exists, and returns it if it does.
1705    /// If no matching subelement exists, tries to create one.
1706    ///
1707    /// # Example
1708    ///
1709    /// ```
1710    /// # use autosar_data::*;
1711    /// # fn main() -> Result<(), AutosarDataError> {
1712    /// # let model = AutosarModel::new();
1713    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1714    /// let ar_packages = model.root_element().get_or_create_sub_element(ElementName::ArPackages)?;
1715    /// let pkg = ar_packages.get_or_create_named_sub_element(ElementName::ArPackage, "Pkg")?;
1716    /// let pkg_2 = ar_packages.get_or_create_named_sub_element(ElementName::ArPackage, "Pkg")?;
1717    /// assert_eq!(pkg, pkg_2);
1718    /// # Ok(())
1719    /// # }
1720    /// ```
1721    ///
1722    /// # Errors
1723    ///
1724    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1725    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1726    ///    The operation was aborted to avoid a deadlock, but can be retried.
1727    ///  - [`AutosarDataError::IncorrectContentType`]: A sub element may not be created in an element with content type `CharacterData`.
1728    ///  - [`AutosarDataError::ElementInsertionConflict`]: The requested sub element cannot be created because it conflicts with an existing sub element.
1729    ///  - [`AutosarDataError::InvalidSubElement`]: The `ElementName` is not a valid sub element according to the specification.
1730    ///  - [`AutosarDataError::ItemNameRequired`]: The sub element requires an item name, so you must use `create_named_sub_element`().
1731    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1732    pub fn get_or_create_named_sub_element(
1733        &self,
1734        element_name: ElementName,
1735        item_name: &str,
1736    ) -> Result<Element, AutosarDataError> {
1737        let (model, version) = self.model_and_version()?;
1738        let mut locked_elem = self.0.try_write().ok_or(AutosarDataError::ParentElementLocked)?;
1739        for item in &locked_elem.content {
1740            if let ElementContent::Element(subelem) = item
1741                && subelem.element_name() == element_name
1742                && subelem.item_name().as_deref().unwrap_or("") == item_name
1743            {
1744                return Ok(subelem.clone());
1745            }
1746        }
1747        locked_elem.create_named_sub_element(self.downgrade(), element_name, item_name, &model, version)
1748    }
1749
1750    /// Create a depth first iterator over this element and all of its sub elements
1751    ///
1752    /// Each step in the iteration returns the depth and an element. Due to the nature of a depth first search,
1753    /// the returned depth can remain the same, increase by one, or decrease by an arbitrary number in each step.
1754    ///
1755    /// The dfs iterator will always return this element as the first item.
1756    ///
1757    /// # Example
1758    ///
1759    /// ```
1760    /// # use autosar_data::*;
1761    /// # let model = AutosarModel::new();
1762    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1763    /// # let element = model.root_element();
1764    /// for (depth, elem) in element.elements_dfs() {
1765    ///     // ...
1766    /// }
1767    /// ```
1768    #[must_use]
1769    pub fn elements_dfs(&self) -> ElementsDfsIterator {
1770        ElementsDfsIterator::new(self, 0)
1771    }
1772
1773    /// Create a depth first iterator over this element and all of its sub elements up to a maximum depth
1774    ///
1775    /// Each step in the iteration returns the depth and an element. Due to the nature of a depth first search,
1776    /// the returned depth can remain the same, increase by one, or decrease by an arbitrary number in each step.
1777    ///
1778    /// The dfs iterator will always return this element as the first item. A `max_depth` of `0` returns all
1779    /// child elements, regardless of depth (like `elements_dfs` does).
1780    ///
1781    /// # Example
1782    ///
1783    /// ```
1784    /// # use autosar_data::*;
1785    /// # let model = AutosarModel::new();
1786    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1787    /// # let element = model.root_element();
1788    /// # element.create_sub_element(ElementName::ArPackages).unwrap();
1789    /// # let sub_elem = element.get_sub_element(ElementName::ArPackages).unwrap();
1790    /// # sub_elem.create_named_sub_element(ElementName::ArPackage, "test2").unwrap();
1791    /// for (depth, elem) in element.elements_dfs_with_max_depth(1) {
1792    ///     assert!(depth <= 1);
1793    ///     // ...
1794    /// }
1795    /// ```
1796    #[must_use]
1797    pub fn elements_dfs_with_max_depth(&self, max_depth: usize) -> ElementsDfsIterator {
1798        ElementsDfsIterator::new(self, max_depth)
1799    }
1800
1801    /// Create an iterator over all the attributes in this element
1802    ///
1803    /// # Example
1804    ///
1805    /// ```
1806    /// # use autosar_data::*;
1807    /// # let model = AutosarModel::new();
1808    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1809    /// # let element = model.root_element();
1810    /// for attribute in element.attributes() {
1811    ///     println!("{} = {}", attribute.attrname, attribute.content);
1812    /// }
1813    /// ```
1814    #[must_use]
1815    pub fn attributes(&self) -> AttributeIterator {
1816        AttributeIterator {
1817            element: self.clone(),
1818            index: 0,
1819        }
1820    }
1821
1822    /// Get the value of an attribute by name
1823    ///
1824    /// # Example
1825    ///
1826    /// ```
1827    /// # use autosar_data::*;
1828    /// # let model = AutosarModel::new();
1829    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1830    /// let value = model.root_element().attribute_value(AttributeName::xsiSchemalocation);
1831    /// ```
1832    #[must_use]
1833    pub fn attribute_value(&self, attrname: AttributeName) -> Option<CharacterData> {
1834        self.0.read().attribute_value(attrname)
1835    }
1836
1837    /// Set the value of a named attribute
1838    ///
1839    /// If no attribute by that name exists, and the attribute is a valid attribute of the element, then the attribute will be created.
1840    ///
1841    /// Returns Ok(()) if the attribute was set, otherwise the Err indicates why setting the attribute failed.
1842    ///
1843    /// ```
1844    /// # use autosar_data::*;
1845    /// # let model = AutosarModel::new();
1846    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1847    /// # let element = model.root_element();
1848    /// let result = element.set_attribute(AttributeName::S, CharacterData::String("1234-5678".to_string()));
1849    /// # assert!(result.is_ok());
1850    /// ```
1851    ///
1852    /// # Errors
1853    ///
1854    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1855    ///  - [`AutosarDataError::InvalidAttribute`]: The `AttributeName` is not valid for this element
1856    ///  - [`AutosarDataError::InvalidAttributeValue`]: The value is not valid for this attribute in this element
1857    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1858    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1859    ///    This happens while determining the Autosar version of the element, so the attribute has not been set in this case.
1860    pub fn set_attribute<T: Into<CharacterData>>(
1861        &self,
1862        attrname: AttributeName,
1863        value: T,
1864    ) -> Result<(), AutosarDataError> {
1865        let version = self.min_version()?;
1866        let (old_base, reference_value) = self.base_attribute_info(attrname);
1867
1868        self.0.write().set_attribute_internal(attrname, value.into(), version)?;
1869
1870        self.base_attribute_fixup(attrname, old_base, reference_value);
1871        Ok(())
1872    }
1873
1874    /// Set the value of a named attribute from a string
1875    ///
1876    /// The function tries to convert the string to the correct data type for the attribute
1877    ///
1878    /// Returns Ok(()) if the attribute was set, otherwise the Err indicates why setting the attribute failed.
1879    ///
1880    /// ```
1881    /// # use autosar_data::*;
1882    /// # let model = AutosarModel::new();
1883    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1884    /// # let element = model.root_element();
1885    /// let result = element.set_attribute_string(AttributeName::T, "2022-01-31T13:59:59Z");
1886    /// # assert!(result.is_ok());
1887    /// ```
1888    ///
1889    /// # Errors
1890    ///
1891    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
1892    ///  - [`AutosarDataError::InvalidAttribute`]: The `AttributeName` is not valid for this element
1893    ///  - [`AutosarDataError::InvalidAttributeValue`]: The value is not valid for this attribute in this element
1894    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
1895    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
1896    ///    This happens while determining the Autosar version of the element, so the attribute has not been set in this case.
1897    pub fn set_attribute_string(&self, attrname: AttributeName, stringvalue: &str) -> Result<(), AutosarDataError> {
1898        let version = self.min_version()?;
1899        let (old_base, reference_value) = self.base_attribute_info(attrname);
1900
1901        self.0.write().set_attribute_string(attrname, stringvalue, version)?;
1902
1903        // post-change fixup for BASE attribute changes only
1904        self.base_attribute_fixup(attrname, old_base, reference_value);
1905        Ok(())
1906    }
1907
1908    /// Remove an attribute from the element
1909    ///
1910    /// Returns true if the attribute existed and could be removed.
1911    ///
1912    /// # Example
1913    ///
1914    /// ```
1915    /// # use autosar_data::*;
1916    /// # let model = AutosarModel::new();
1917    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1918    /// let result = model.root_element().remove_attribute(AttributeName::xsiSchemalocation);
1919    /// // xsiSchemalocation exists in the AUTOSAR element, but it is mandatory and cannot be removed
1920    /// assert_eq!(result, false);
1921    /// ```
1922    #[must_use]
1923    pub fn remove_attribute(&self, attrname: AttributeName) -> bool {
1924        let (old_base, reference_value) = self.base_attribute_info(attrname);
1925
1926        let removed = self.0.write().remove_attribute(attrname);
1927
1928        if removed {
1929            // fix the cache of reference origins if a BASE attribute was removed
1930            self.base_attribute_fixup(attrname, old_base, reference_value);
1931        }
1932        removed
1933    }
1934
1935    /// helper function to get the old BASE attribute value and the reference value before changing or removing the attribute
1936    fn base_attribute_info(&self, attrname: AttributeName) -> (Option<String>, Option<String>) {
1937        if !self.is_reference() || attrname != AttributeName::Base {
1938            return (None, None);
1939        }
1940        let old_base = self
1941            .attribute_value(AttributeName::Base)
1942            .and_then(|cdata| cdata.string_value());
1943        let reference_value = self.character_data().and_then(|cdata| cdata.string_value());
1944        (old_base, reference_value)
1945    }
1946
1947    /// Fix the caches affected by a reference element's BASE attribute being changed or removed
1948    fn base_attribute_fixup(&self, attrname: AttributeName, old_base: Option<String>, reference_value: Option<String>) {
1949        if attrname != AttributeName::Base {
1950            return;
1951        }
1952        if let Some(reference_value) = reference_value
1953            && let Ok(model) = self.model()
1954        {
1955            let new_base = self
1956                .attribute_value(AttributeName::Base)
1957                .and_then(|cdata| cdata.string_value());
1958            if old_base != new_base {
1959                model.fix_reference_origins(
1960                    &reference_value,
1961                    &reference_value,
1962                    new_base.as_deref(),
1963                    self.downgrade(),
1964                );
1965            }
1966        }
1967        // The BASE attribute of the PACKAGE-REF of a REFERENCE-BASE selects the reference base that
1968        // the PACKAGE-REF is itself relative to, so this can change what the relative references in the
1969        // scope of that REFERENCE-BASE resolve to.
1970        if let Ok(model) = self.model() {
1971            model.resolve_relative_references();
1972        }
1973    }
1974
1975    /// Recursively sort all sub-elements of this element
1976    ///
1977    /// All sub elements of the current element are sorted alphabetically.
1978    /// If the sub-elements are named, then the sorting is performed according to the item names,
1979    /// otherwise the serialized form of the sub-elements is used for sorting.
1980    /// Element attributes are not taken into account while sorting.
1981    /// The elements are sorted in place, and sorting cannot fail, so there is no return value.
1982    ///
1983    /// # Example
1984    /// ```
1985    /// # use autosar_data::*;
1986    /// # let model = AutosarModel::new();
1987    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
1988    /// # let element = model.root_element();
1989    /// element.sort();
1990    /// ```
1991    pub fn sort(&self) {
1992        self.0.write().sort();
1993    }
1994
1995    /// Serialize the element and all of its content to a string
1996    ///
1997    /// The serialized text generated for elements below the root element cannot be loaded, but it may be useful for display.
1998    ///
1999    /// # Example
2000    ///
2001    /// ```
2002    /// # use autosar_data::*;
2003    /// # let model = AutosarModel::new();
2004    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2005    /// # let element = model.root_element();
2006    /// let text = element.serialize();
2007    /// ```
2008    #[must_use]
2009    pub fn serialize(&self) -> String {
2010        let mut outstring = String::new();
2011
2012        self.0.read().serialize_internal(&mut outstring, 0, false, &None, None);
2013
2014        outstring
2015    }
2016
2017    pub(crate) fn elemtype(&self) -> ElementType {
2018        self.0.read().elemtype
2019    }
2020
2021    // an element might have a diffeent element type depending on the version - as a result of a
2022    // changed datatype of the CharacterData, or because the element ordering was changed
2023    fn recalc_element_type(&self, target_version: AutosarVersion) -> ElementType {
2024        if let Ok(Some(parent)) = self.parent()
2025            && let Some((etype, ..)) = parent
2026                .element_type()
2027                .find_sub_element(self.element_name(), target_version as u32)
2028        {
2029            return etype;
2030        }
2031
2032        self.element_type()
2033    }
2034
2035    /// check if the sub elements and attributes of this element are compatible with some `target_version`
2036    pub(crate) fn check_version_compatibility(
2037        &self,
2038        file: &WeakArxmlFile,
2039        target_version: AutosarVersion,
2040    ) -> (Vec<CompatibilityError>, u32) {
2041        let mut compat_errors = Vec::new();
2042        let mut overall_version_mask = u32::MAX;
2043
2044        // make sure compatibility checks are performed with the element type used in the target version
2045        let elemtype_new = self.recalc_element_type(target_version);
2046
2047        // check the compatibility of all the attributes in this element
2048        {
2049            let element = self.0.read();
2050            for attribute in &element.attributes {
2051                // find the specification for the current attribute
2052                if let Some(AttributeSpec {
2053                    spec: value_spec,
2054                    version: version_mask,
2055                    ..
2056                }) = elemtype_new.find_attribute_spec(attribute.attrname)
2057                {
2058                    overall_version_mask &= version_mask;
2059                    // check if the attribute is allowed at all
2060                    if !target_version.compatible(version_mask) {
2061                        compat_errors.push(CompatibilityError::IncompatibleAttribute {
2062                            element: self.clone(),
2063                            attribute: attribute.attrname,
2064                            version_mask,
2065                        });
2066                    } else {
2067                        let (is_compatible, value_version_mask) = attribute
2068                            .content
2069                            .check_version_compatibility(value_spec, target_version);
2070                        if !is_compatible {
2071                            compat_errors.push(CompatibilityError::IncompatibleAttributeValue {
2072                                element: self.clone(),
2073                                attribute: attribute.attrname,
2074                                attribute_value: attribute.content.to_string(),
2075                                version_mask: value_version_mask,
2076                            });
2077                        }
2078                        overall_version_mask &= value_version_mask;
2079                    }
2080                }
2081            }
2082        }
2083
2084        // check the compatibility of all sub-elements
2085        for sub_element in self.sub_elements() {
2086            if sub_element.0.read().is_in_file(file)
2087                && let Some((_, indices)) = elemtype_new
2088                    .find_sub_element(sub_element.element_name(), target_version as u32)
2089                    .or(elemtype_new.find_sub_element(sub_element.element_name(), u32::MAX))
2090                && let Some(version_mask) = elemtype_new.get_sub_element_version_mask(&indices)
2091            {
2092                overall_version_mask &= version_mask;
2093                if !target_version.compatible(version_mask) {
2094                    compat_errors.push(CompatibilityError::IncompatibleElement {
2095                        element: sub_element.clone(),
2096                        version_mask,
2097                    });
2098                } else {
2099                    let (mut sub_element_errors, sub_element_mask) =
2100                        sub_element.check_version_compatibility(file, target_version);
2101                    compat_errors.append(&mut sub_element_errors);
2102                    overall_version_mask &= sub_element_mask;
2103                }
2104            }
2105        }
2106
2107        (compat_errors, overall_version_mask)
2108    }
2109
2110    /// List all `sub_elements` that are valid in the current element
2111    ///
2112    /// The target use case is direct interaction with a user, e.g. through a selection dialog
2113    ///
2114    /// # Return Value
2115    ///
2116    /// A list of tuples consisting of
2117    ///     `ElementName` of the potential sub element
2118    ///     bool: is the sub element named
2119    ///     bool: can this sub element be inserted considering the current content of the element
2120    ///
2121    /// # Example
2122    ///
2123    /// ```
2124    /// # use autosar_data::*;
2125    /// # let model = AutosarModel::new();
2126    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2127    /// # let element = model.root_element();
2128    /// for ValidSubElementInfo{element_name, is_named, is_allowed} in element.list_valid_sub_elements() {
2129    ///     // ...
2130    /// }
2131    /// ```
2132    #[must_use]
2133    pub fn list_valid_sub_elements(&self) -> Vec<ValidSubElementInfo> {
2134        let etype = self.0.read().elemtype;
2135        let mut valid_sub_elements = Vec::new();
2136
2137        if let Ok(version) = self.min_version() {
2138            for (element_name, _, version_mask, named_mask) in etype.sub_element_spec_iter() {
2139                if version.compatible(version_mask) {
2140                    let is_named = version.compatible(named_mask);
2141                    let is_allowed = self.0.read().calc_element_insert_range(element_name, version).is_ok();
2142                    valid_sub_elements.push(ValidSubElementInfo {
2143                        element_name,
2144                        is_named,
2145                        is_allowed,
2146                    });
2147                }
2148            }
2149        }
2150
2151        valid_sub_elements
2152    }
2153
2154    /// Return the set of files in which the current element is present
2155    ///
2156    /// # Return Value
2157    ///
2158    /// A tuple (bool, `HashSet`); if the bool value is true, then the file set is stored in this element, otherwise it is inherited from a parent element.
2159    ///
2160    /// # Example
2161    ///
2162    /// ```
2163    /// # use autosar_data::*;
2164    /// # fn main() -> Result<(), AutosarDataError> {
2165    /// # let model = AutosarModel::new();
2166    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2167    /// # let element = model.root_element();
2168    /// let (inherited, file_membership) = element.file_membership()?;
2169    /// # Ok(())
2170    /// # }
2171    /// ```
2172    /// # Errors
2173    ///
2174    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
2175    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
2176    ///    The operation was aborted to avoid a deadlock, but can be retried.
2177    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
2178    pub fn file_membership(&self) -> Result<(bool, HashSet<WeakArxmlFile>), AutosarDataError> {
2179        let mut cur_elem_opt = Some(self.clone());
2180        while let Some(cur_elem) = &cur_elem_opt {
2181            let locked_cur_elem = cur_elem
2182                .0
2183                .try_read_for(LOCK_CONTENTION_TIMEOUT)
2184                .ok_or(AutosarDataError::ParentElementLocked)?;
2185            if let Some(files) = locked_cur_elem.file_membership.as_deref() {
2186                return Ok((cur_elem == self, files.clone()));
2187            }
2188            let parent = locked_cur_elem.parent()?;
2189            drop(locked_cur_elem);
2190
2191            cur_elem_opt = parent;
2192        }
2193
2194        // no file membership info found at any level - this only happens if the model does not contain any files
2195        Err(AutosarDataError::NoFilesInModel)
2196    }
2197
2198    /// return the file membership of this element without trying to get an inherited value
2199    pub(crate) fn file_membership_local(&self) -> HashSet<WeakArxmlFile> {
2200        self.0.read().file_membership_cloned()
2201    }
2202
2203    /// set the file membership of an element
2204    ///
2205    /// The passed set acts as a restriction of the file membership of the parent element.
2206    /// This means that the set of a child cannot be greater than that of the parent.
2207    ///
2208    /// Setting an empty set has a special meaning: it reverts the membership to default,
2209    /// i.e. inherited from the parent with no additional restriction
2210    pub(crate) fn set_file_membership(&self, file_membership: HashSet<WeakArxmlFile>) {
2211        // find out if the parent is splittable. If the parent is unavaliable, assume
2212        // that the caller knows what they're doing and assume it is splittable
2213        let parent_splittable = self
2214            .parent()
2215            .ok()
2216            .flatten()
2217            .map_or(u32::MAX, |p| p.element_type().splittable());
2218        // can always reset the membership to empty = inherited; otherwise the parent must be splittable
2219        if file_membership.is_empty() || parent_splittable != 0 {
2220            self.0.write().set_file_membership(file_membership);
2221        }
2222    }
2223
2224    /// add the current element to the given file
2225    ///
2226    /// In order to successfully cause the element to appear in the serialized file data, all parent elements
2227    /// of the current element will also be added if required.
2228    ///
2229    /// If the model only has a single file then this function does nothing.
2230    ///
2231    /// # Example
2232    /// ```
2233    /// # use autosar_data::*;
2234    /// # use std::collections::HashSet;
2235    /// # let model = AutosarModel::new();
2236    /// let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2237    /// # let element = model.root_element();
2238    /// element.add_to_file(&file);
2239    /// ```
2240    ///
2241    /// # Errors
2242    ///
2243    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
2244    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
2245    ///    The operation was aborted to avoid a deadlock, but can be retried.
2246    ///
2247    pub fn add_to_file(&self, file: &ArxmlFile) -> Result<(), AutosarDataError> {
2248        let parent_splittable = self.parent()?.is_none_or(|p| p.element_type().splittable() != 0);
2249        if parent_splittable {
2250            if file.model()? == self.model()? {
2251                let weak_file = file.downgrade();
2252                // current_fileset is the set of files which contain the current element
2253                let (_, current_fileset) = self.file_membership()?;
2254                // if the model only has a single file or if the element is already in the set then there is nothing to do
2255                if !current_fileset.contains(&weak_file) {
2256                    let mut updated_fileset = current_fileset;
2257                    updated_fileset.insert(weak_file);
2258                    self.0.write().set_file_membership(updated_fileset);
2259
2260                    // recursively continue with the parent
2261                    if let Some(parent) = self.parent()? {
2262                        parent.add_to_file_restricted(file)?;
2263                    }
2264                }
2265                Ok(())
2266            } else {
2267                // adding a file from a different model is not permitted
2268                Err(AutosarDataError::InvalidFile)
2269            }
2270        } else {
2271            Err(AutosarDataError::FilesetModificationForbidden)
2272        }
2273    }
2274
2275    /// add only this element and its direct parents to a file, but not its children
2276    pub(crate) fn add_to_file_restricted(&self, file: &ArxmlFile) -> Result<(), AutosarDataError> {
2277        let weak_file = file.downgrade();
2278        let (local, current_fileset) = self.file_membership().unwrap_or((true, HashSet::new()));
2279
2280        if !current_fileset.contains(&weak_file) {
2281            // if the current element is splittable, then all of its subelements are allowed to have their own filesets
2282            // unless something else is already set, they should get the current unmodified file membership of this element
2283            // which does not include the new file
2284            if self.element_type().splittable() != 0 {
2285                for se in self.sub_elements() {
2286                    if let Some(mut subelem) = se.0.try_write_for(LOCK_CONTENTION_TIMEOUT)
2287                        && subelem.file_membership.is_none()
2288                    {
2289                        subelem.set_file_membership(current_fileset.clone());
2290                    }
2291                }
2292            }
2293
2294            let mut extended_fileset = current_fileset;
2295            extended_fileset.insert(weak_file);
2296            // if the parent is splittable, or if the current element already has a fileset, then that fileset should be updated
2297            let parent_splittable = self.parent()?.is_none_or(|p| p.element_type().splittable() != 0);
2298            if parent_splittable || local {
2299                self.0.write().set_file_membership(extended_fileset);
2300            }
2301
2302            // recursively continue with the parent
2303            if let Some(parent) = self.parent()? {
2304                parent.add_to_file_restricted(file)?;
2305            }
2306        }
2307
2308        Ok(())
2309    }
2310
2311    /// remove this element from a file
2312    ///
2313    /// If the model consists of multiple files, then the set of files in
2314    /// which this element appears will be restricted.
2315    /// It may be required to also omit its parent(s), up to the next splittable point.
2316    ///
2317    /// If the element is only present in single file then an attempt to delete it will be made instead.
2318    /// Deleting the element fails if the element is the root AUTOSAR element, or if it is a SHORT-NAME.
2319    ///
2320    /// # Example
2321    /// ```
2322    /// # use autosar_data::*;
2323    /// # use std::collections::HashSet;
2324    /// # let model = AutosarModel::new();
2325    /// # let file = model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
2326    /// # let file2 = model.create_file("test2", AutosarVersion::Autosar_00050).unwrap();
2327    /// # let element = model.root_element();
2328    /// assert!(model.files().count() > 1);
2329    /// element.remove_from_file(&file);
2330    /// ```
2331    ///
2332    /// # Errors
2333    ///
2334    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
2335    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
2336    ///    The operation was aborted to avoid a deadlock, but can be retried.
2337    ///  - [`AutosarDataError::InvalidFile`]: The file is part of a different model
2338    ///  - [`AutosarDataError::FilesetModificationForbidden`]: The parent of the current element is not splittable, so the fileset of the current element cannot be modified
2339    ///  - [`AutosarDataError::RootElementRemovalForbidden`]: The current element is the AUTOSAR root element, which cannot be removed from the last file containing it
2340    ///  - [`AutosarDataError::ShortNameRemovalForbidden`]: The current element is a SHORT-NAME that would have to be deleted, which is not permitted
2341    ///
2342    pub fn remove_from_file(&self, file: &ArxmlFile) -> Result<(), AutosarDataError> {
2343        let parent_splittable = self.parent()?.is_none_or(|p| p.element_type().splittable() != 0);
2344        if parent_splittable {
2345            if file.model()? == self.model()? {
2346                let weak_file = file.downgrade();
2347
2348                // current_fileset is the set of files which contain the current element
2349                let (_, current_fileset) = self.file_membership()?;
2350                let mut restricted_fileset = current_fileset;
2351                restricted_fileset.remove(&weak_file);
2352                if restricted_fileset.is_empty() {
2353                    // the element would no longer be part of any file, so it must be deleted instead.
2354                    let Some(parent) = self.parent()? else {
2355                        return Err(AutosarDataError::RootElementRemovalForbidden);
2356                    };
2357                    parent.remove_sub_element(self.to_owned())
2358                } else {
2359                    self.0.write().set_file_membership(restricted_fileset);
2360
2361                    // update all sub elements with non-default file_membership
2362                    let mut to_delete = Vec::new();
2363                    for (_, subelem) in self.elements_dfs() {
2364                        // only need to care about those where file_membership is not empty. All other inherit from their parent
2365                        if subelem.0.read().file_membership.is_some() {
2366                            subelem.0.write().remove_file_membership(&weak_file);
2367                            // removing the last file resets the membership to inherited, which means
2368                            // that subelem is no longer in any file and has to be deleted
2369                            if subelem.0.read().file_membership.is_none() {
2370                                to_delete.push(subelem);
2371                            }
2372                        }
2373                    }
2374                    // delete elements that are no longer in any file
2375                    for delete_elem in to_delete {
2376                        if let Ok(Some(parent)) = delete_elem.parent() {
2377                            let _ = parent.remove_sub_element(delete_elem);
2378                        }
2379                    }
2380
2381                    Ok(())
2382                }
2383            } else {
2384                // adding a file from a different model is not permitted
2385                Err(AutosarDataError::InvalidFile)
2386            }
2387        } else {
2388            Err(AutosarDataError::FilesetModificationForbidden)
2389        }
2390    }
2391
2392    /// Return a path that includes non-identifiable elements by their xml names
2393    ///
2394    /// This function cannot fail completely, it will always collect as much information as possible.
2395    /// It is intended for display in error messages.
2396    #[must_use]
2397    pub fn xml_path(&self) -> String {
2398        self.0.read().xml_path()
2399    }
2400
2401    /// Find the upper and lower bound on the insert position for a new sub element
2402    ///
2403    /// If the sub element is allowed for this element given its current content, this function
2404    /// returns the lower and upper bound on the position the new sub element could have.
2405    /// If the sub element is not allowed, then an Err is returned instead.
2406    ///
2407    /// The lower and upper bounds are inclusive: lower <= (element insert pos) <= upper.
2408    /// In many situations lower == upper, this means there is only a single valid position.
2409    ///
2410    /// # Example
2411    /// ```
2412    /// # use autosar_data::*;
2413    /// # fn main() -> Result<(), AutosarDataError> {
2414    /// # use std::collections::HashSet;
2415    /// # let model = AutosarModel::new();
2416    /// # model.create_file("test", AutosarVersion::LATEST).unwrap();
2417    /// let (lbound, ubound) = model.root_element()
2418    ///     .calc_element_insert_range(ElementName::ArPackages, AutosarVersion::LATEST)?;
2419    /// model.root_element().create_sub_element_at(ElementName::ArPackages, lbound)?;
2420    /// # Ok(())
2421    /// # }
2422    /// ```
2423    ///
2424    /// # Errors
2425    ///
2426    /// - [`AutosarDataError::ElementInsertionConflict`]: The sub element conflicts with an existing sub element
2427    /// - [`AutosarDataError::InvalidSubElement`]: The sub element is not valid inside this element
2428    pub fn calc_element_insert_range(
2429        &self,
2430        element_name: ElementName,
2431        version: AutosarVersion,
2432    ) -> Result<(usize, usize), AutosarDataError> {
2433        self.0.read().calc_element_insert_range(element_name, version)
2434    }
2435
2436    /// Return the comment attachd to the element (if any)
2437    ///
2438    /// A comment directly preceding the opening tag is considered to be atached and is returned here.
2439    ///
2440    /// In the arxml text:
2441    /// ```xml
2442    ///     <!--element comment-->
2443    ///     <ELEMENT> ...
2444    /// ```
2445    ///
2446    /// # Example
2447    ///
2448    /// ```
2449    /// # use autosar_data::*;
2450    /// # use std::collections::HashSet;
2451    /// # let model = AutosarModel::new();
2452    /// # let file = model.create_file("test", AutosarVersion::LATEST).unwrap();
2453    /// # let element = model.root_element();
2454    /// let opt_comment = element.comment();
2455    /// ```
2456    #[must_use]
2457    pub fn comment(&self) -> Option<String> {
2458        self.0.read().comment.clone()
2459    }
2460
2461    /// Set or delete the comment attached to the element
2462    ///
2463    /// Set None to remove the comment.
2464    ///
2465    /// If the new comment value contains "--", then this is replaced with "__", because "--" is forbidden inside XML comments.
2466    ///
2467    /// # Example
2468    ///
2469    /// ```
2470    /// # use autosar_data::*;
2471    /// # use std::collections::HashSet;
2472    /// # let model = AutosarModel::new();
2473    /// # let file = model.create_file("test", AutosarVersion::LATEST).unwrap();
2474    /// # let element = model.root_element();
2475    /// # let string = "".to_string();
2476    /// element.set_comment(Some(string));
2477    /// ```
2478    pub fn set_comment(&self, mut opt_comment: Option<String>) {
2479        if let Some(comment) = &mut opt_comment {
2480            // make sure the comment we store never contains "--" as this is forbidden by the w3 xml specification
2481            if comment.contains("--") {
2482                *comment = comment.replace("--", "__");
2483            }
2484        }
2485        self.0.write().comment = opt_comment;
2486    }
2487
2488    /// find the minumum version of all arxml files which contain this element
2489    ///
2490    /// Typically this reduces to finding out which single file contains the element and returning this version.
2491    ///
2492    /// # Example
2493    ///
2494    /// ```
2495    /// # use autosar_data::*;
2496    /// # use std::collections::HashSet;
2497    /// # let model = AutosarModel::new();
2498    /// let file1 = model.create_file("file1", AutosarVersion::LATEST).unwrap();
2499    /// let file2 = model.create_file("file2", AutosarVersion::Autosar_4_3_0).unwrap();
2500    /// let version = model.root_element().min_version().unwrap();
2501    /// assert_eq!(version, AutosarVersion::Autosar_4_3_0);
2502    /// ```
2503    ///
2504    /// # Errors
2505    ///
2506    ///  - [`AutosarDataError::ItemDeleted`]: The current element is in the deleted state and will be freed once the last reference is dropped
2507    ///  - [`AutosarDataError::ParentElementLocked`]: a parent element was locked and did not become available after waiting briefly.
2508    ///    The operation was aborted to avoid a deadlock, but can be retried.
2509    ///  - [`AutosarDataError::NoFilesInModel`]: The operation cannot be completed because the model does not contain any files
2510    pub fn min_version(&self) -> Result<AutosarVersion, AutosarDataError> {
2511        let model = self.model()?;
2512        self.min_version_in(&model)
2513    }
2514
2515    /// get the model of this element together with its minimum Autosar version
2516    ///
2517    /// The element creation and modification functions need both values, and getting the version
2518    /// requires the model, so getting them separately would look up the model twice.
2519    pub(crate) fn model_and_version(&self) -> Result<(AutosarModel, AutosarVersion), AutosarDataError> {
2520        let model = self.model()?;
2521        let version = self.min_version_in(&model)?;
2522        Ok((model, version))
2523    }
2524
2525    /// get the minimum Autosar version of this element, given the model it belongs to
2526    fn min_version_in(&self, model: &AutosarModel) -> Result<AutosarVersion, AutosarDataError> {
2527        if let Some(version) = model.single_file_version() {
2528            // every element of a model with a single file belongs to that file, so its version
2529            // applies without looking up the file membership
2530            return Ok(version);
2531        }
2532
2533        // the model is split across several files, so the file membership of the element decides.
2534        // It is inherited from the closest parent element that has one.
2535        let mut cur_elem_opt = Some(self.clone());
2536        while let Some(cur_elem) = &cur_elem_opt {
2537            let locked_cur_elem = cur_elem
2538                .0
2539                .try_read_for(LOCK_CONTENTION_TIMEOUT)
2540                .ok_or(AutosarDataError::ParentElementLocked)?;
2541            if let Some(files) = locked_cur_elem.file_membership.as_deref() {
2542                let ver = files
2543                    .iter()
2544                    .filter_map(WeakArxmlFile::upgrade)
2545                    .map(|f| f.version())
2546                    .min()
2547                    .unwrap_or(AutosarVersion::LATEST);
2548
2549                return Ok(ver);
2550            }
2551            // read the parent while the lock of the current element is still held: going through
2552            // Element::parent() instead would acquire the same lock a second time on every level
2553            let parent = locked_cur_elem.parent()?;
2554            drop(locked_cur_elem);
2555
2556            cur_elem_opt = parent;
2557        }
2558
2559        Err(AutosarDataError::NoFilesInModel)
2560    }
2561}
2562
2563impl Ord for Element {
2564    /// compare the content of two elements
2565    ///
2566    /// This function compares the content of two elements, returning a cmp::Ordering value.
2567    /// The purpose of this function is to allow sorting of elements based on their content.
2568    ///
2569    /// The comparison is performed in the following order:
2570    /// 1. Element name
2571    /// 2. Index (if present)
2572    /// 3. Item name (if present)
2573    /// 4. Definition reference (if present)
2574    /// 5. DEST attribute (if present)
2575    /// 6. Content of the element
2576    /// 7. Attributes of the element
2577    ///
2578    /// If the comparison returns `Ordering::Equal`, then the two elements are identical, but this does not imply that they are the same object.
2579    ///
2580    /// # Example
2581    /// ```
2582    /// # use autosar_data::*;
2583    /// # let model = AutosarModel::new();
2584    /// # let file = model.create_file("test", AutosarVersion::LATEST).unwrap();
2585    /// # let element1 = model.root_element();
2586    /// # let element2 = model.root_element();
2587    /// let ordering = element1.cmp(&element2);
2588    /// ```
2589    fn cmp(&self, other: &Element) -> std::cmp::Ordering {
2590        // Sort by the element name first. This test prevents the other comparisons from being performed when they don't make sense.
2591        match self.element_name().to_str().cmp(other.element_name().to_str()) {
2592            Ordering::Equal => {}
2593            other => return other,
2594        }
2595
2596        // if both elements have an index, then compare the index; if only one has an index, then it comes first
2597        // if neither has an index, then continue and compare other criteria
2598        let index1 = self
2599            .get_sub_element(ElementName::Index)
2600            .and_then(|indexelem| indexelem.character_data())
2601            .and_then(|cdata| cdata.parse_integer::<u64>());
2602        let index2 = other
2603            .get_sub_element(ElementName::Index)
2604            .and_then(|indexelem| indexelem.character_data())
2605            .and_then(|cdata| cdata.parse_integer::<u64>());
2606        match (index1, index2) {
2607            (Some(idx1), Some(idx2)) => {
2608                let result = idx1.cmp(&idx2);
2609                if result != Ordering::Equal {
2610                    return result;
2611                }
2612            }
2613            (Some(_), None) => return std::cmp::Ordering::Less,
2614            (None, Some(_)) => return std::cmp::Ordering::Greater,
2615            (None, None) => {}
2616        }
2617
2618        // sort by item name if present
2619        if let (Some(name1), Some(name2)) = (self.item_name(), other.item_name()) {
2620            // both items have a name - try to decompose the name into a base and an index
2621            // this allows for a more natural sorting of indexed items (e.g. "item2" < "item10")
2622            if let (Some((base1, idx1)), Some((base2, idx2))) =
2623                (decompose_item_name(&name1), decompose_item_name(&name2))
2624                && base1 == base2
2625            {
2626                let result = idx1.cmp(&idx2);
2627                if result != Ordering::Equal {
2628                    return result;
2629                }
2630            }
2631            // if the decomposition fails, then just compare the full item names
2632            let result = name1.cmp(&name2);
2633            if result != Ordering::Equal {
2634                return result;
2635            }
2636        }
2637
2638        // for BSW values: compare the definition references
2639        let definition1 = self
2640            .get_sub_element(ElementName::DefinitionRef)
2641            .and_then(|defref| defref.character_data())
2642            .and_then(|cdata| cdata.string_value());
2643        let definition2 = other
2644            .get_sub_element(ElementName::DefinitionRef)
2645            .and_then(|defref| defref.character_data())
2646            .and_then(|cdata| cdata.string_value());
2647        if let (Some(def1), Some(def2)) = (definition1, definition2) {
2648            let result = def1.cmp(&def2);
2649            if result != Ordering::Equal {
2650                return result;
2651            }
2652        }
2653
2654        // for references: compare the DEST attribute
2655        let dest1 = self
2656            .attribute_value(AttributeName::Dest)
2657            .and_then(|cdata| cdata.enum_value());
2658        let dest2 = other
2659            .attribute_value(AttributeName::Dest)
2660            .and_then(|cdata| cdata.enum_value());
2661        match (dest1, dest2) {
2662            (Some(dest1), Some(dest2)) => {
2663                let result = dest1.to_str().cmp(dest2.to_str());
2664                if result != Ordering::Equal {
2665                    return result;
2666                }
2667            }
2668            (Some(_), None) => return std::cmp::Ordering::Less,
2669            (None, Some(_)) => return std::cmp::Ordering::Greater,
2670            (None, None) => {}
2671        }
2672
2673        // if all else fails, compare the content of the elements
2674        let locked_self = self.0.read();
2675        let locked_other = other.0.read();
2676        locked_self
2677            .content
2678            .cmp(&locked_other.content)
2679            .then(locked_self.attributes.cmp(&locked_other.attributes))
2680    }
2681}
2682
2683impl PartialOrd for Element {
2684    fn partial_cmp(&self, other: &Element) -> Option<std::cmp::Ordering> {
2685        Some(self.cmp(other))
2686    }
2687}
2688
2689/// decompose an item name into a base name and an index
2690/// The index is expected to be a decimal number at the end of the string
2691///
2692/// E.g. "item123" -> ("item", 123)
2693fn decompose_item_name(name: &str) -> Option<(String, u64)> {
2694    let bytestr = name.as_bytes();
2695    let mut pos = bytestr.len();
2696    while pos > 0 && bytestr[pos - 1].is_ascii_digit() {
2697        pos -= 1;
2698    }
2699    if let Ok(index) = name[pos..].parse() {
2700        Some((name[0..pos].to_owned(), index))
2701    } else {
2702        None
2703    }
2704}
2705
2706// a helper that provides compact debug output for the content of an element
2707struct ElementContentFormatter<'a>(&'a SmallVec<[ElementContent; 4]>);
2708impl std::fmt::Debug for ElementContentFormatter<'_> {
2709    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2710        let mut list_fmt = f.debug_list();
2711        for item in self.0.iter() {
2712            match item {
2713                ElementContent::Element(elem) => list_fmt.entry(&elem.element_name()),
2714                ElementContent::CharacterData(cdata) => list_fmt.entry(&cdata),
2715            };
2716        }
2717        list_fmt.finish()
2718    }
2719}
2720
2721// A custom type is needed in order to print a custom value in the Debug implementation without double quoting
2722struct DebugDisplay<'a>(&'a str);
2723impl std::fmt::Debug for DebugDisplay<'_> {
2724    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2725        f.write_str(self.0)
2726    }
2727}
2728
2729// custom debug implementation: print the content instead of only showing the pointer of the Arc
2730impl std::fmt::Debug for Element {
2731    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2732        let elem = self.0.read();
2733        let mut dbgstruct = f.debug_struct("Element");
2734        if let Some(name) = elem.item_name() {
2735            dbgstruct.field("name", &name);
2736        }
2737        dbgstruct.field("elemname", &elem.elemname);
2738        dbgstruct.field("elemtype", &elem.elemtype);
2739        dbgstruct.field("parent", &elem.parent);
2740        dbgstruct.field("content", &ElementContentFormatter(&elem.content));
2741        dbgstruct.field("attributes", &elem.attributes);
2742        // only print the file membership if the element is splittable
2743        // elements that are not splittable may not modify their file membership
2744        if elem.elemtype.splittable() != 0 {
2745            if elem.file_membership.is_none() {
2746                dbgstruct.field("file_membership", &DebugDisplay("(inherited)"));
2747            } else {
2748                dbgstruct.field("file_membership", &elem.file_membership);
2749            }
2750        }
2751        dbgstruct.finish()
2752    }
2753}
2754
2755impl PartialEq for Element {
2756    fn eq(&self, other: &Self) -> bool {
2757        Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
2758    }
2759}
2760
2761impl Eq for Element {}
2762
2763impl Hash for Element {
2764    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2765        state.write_usize(Arc::as_ptr(&self.0) as usize);
2766    }
2767}
2768
2769impl std::fmt::Debug for WeakElement {
2770    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2771        if let Some(elem) = self.upgrade() {
2772            // write!(f, "Element:WeakRef {:p}", Arc::as_ptr(&elem.0));
2773            f.write_fmt(format_args!("Element:WeakRef ({})", elem.element_name()))
2774        } else {
2775            f.write_fmt(format_args!("Element:WeakRef {:p} (invalid)", Weak::as_ptr(&self.0)))
2776        }
2777    }
2778}
2779
2780impl WeakElement {
2781    /// try to get a strong reference to the [Element]
2782    pub fn upgrade(&self) -> Option<Element> {
2783        Weak::upgrade(&self.0).map(Element)
2784    }
2785}
2786
2787impl PartialEq for WeakElement {
2788    fn eq(&self, other: &Self) -> bool {
2789        Weak::as_ptr(&self.0) == Weak::as_ptr(&other.0)
2790    }
2791}
2792
2793impl Eq for WeakElement {}
2794
2795impl Hash for WeakElement {
2796    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
2797        state.write_usize(Weak::as_ptr(&self.0) as usize);
2798    }
2799}
2800
2801impl ElementContent {
2802    /// returns the element contained inside this `ElementContent`, or None if the content is `CharacterData`
2803    #[must_use]
2804    pub fn unwrap_element(&self) -> Option<Element> {
2805        if let ElementContent::Element(element) = self {
2806            Some(element.clone())
2807        } else {
2808            None
2809        }
2810    }
2811
2812    /// returns the `CharacterData` inside this `ElementContent`, or None if the content is an Element
2813    #[must_use]
2814    pub fn unwrap_cdata(&self) -> Option<CharacterData> {
2815        if let ElementContent::CharacterData(cdata) = self {
2816            Some(cdata.clone())
2817        } else {
2818            None
2819        }
2820    }
2821}
2822
2823// custom debug implementation: skip printing any content, since the content is only "WeakRef(0x...)"
2824impl std::fmt::Debug for ElementOrModel {
2825    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2826        match self {
2827            ElementOrModel::Element(..) => f.write_str("Element"),
2828            ElementOrModel::Model(_) => f.write_str("Model"),
2829            ElementOrModel::None => f.write_str("None/Invalid"),
2830        }
2831    }
2832}
2833
2834// custom debug implementation: skip printing the name of the wrapper-enum and directly show the content
2835impl std::fmt::Debug for ElementContent {
2836    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2837        match self {
2838            ElementContent::Element(elem) => elem.fmt(f),
2839            ElementContent::CharacterData(cdata) => cdata.fmt(f),
2840        }
2841    }
2842}
2843
2844#[cfg(test)]
2845mod test {
2846    use crate::*;
2847    use std::ffi::OsString;
2848
2849    const BASIC_AUTOSAR_FILE: &str = r#"<?xml version="1.0" encoding="utf-8"?>
2850    <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">
2851        <AR-PACKAGES>
2852            <AR-PACKAGE>
2853                <SHORT-NAME>TestPackage</SHORT-NAME>
2854            </AR-PACKAGE>
2855        </AR-PACKAGES>
2856    </AUTOSAR>"#;
2857
2858    #[test]
2859    fn element_creation() {
2860        let model = AutosarModel::new();
2861        model
2862            .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
2863            .unwrap();
2864        let el_autosar = model.root_element();
2865        let el_ar_package = model.get_element_by_path("/TestPackage").unwrap();
2866
2867        let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
2868        let el_compu_method = el_elements
2869            .create_named_sub_element(ElementName::CompuMethod, "TestCompuMethod")
2870            .unwrap();
2871        el_elements
2872            .create_named_sub_element(ElementName::CompuMethod, "TestCompuMethod2")
2873            .unwrap();
2874        el_elements
2875            .create_named_sub_element(ElementName::CompuMethod, "TestCompuMethod3")
2876            .unwrap();
2877        // elements with duplicate names are not allowed
2878        assert!(
2879            el_elements
2880                .create_named_sub_element(ElementName::CompuMethod, "TestCompuMethod3")
2881                .is_err()
2882        );
2883
2884        let count = el_elements.sub_elements().count();
2885        assert_eq!(count, 3);
2886        assert_eq!(count, el_elements.content_item_count());
2887
2888        // inserting another COMPU-METHOD into ELEMENTS hould be allowed at any position
2889        let (start_pos, end_pos) = el_elements
2890            .0
2891            .read()
2892            .calc_element_insert_range(ElementName::CompuMethod, AutosarVersion::Autosar_00050)
2893            .unwrap();
2894        assert_eq!(start_pos, 0);
2895        assert_eq!(end_pos, 3); // upper limit is 3 since there are currently 3 elements
2896
2897        // check if create_named_sub_element correctly registered the element in the hashmap so that it can be found
2898        let el_compu_method_test = model.get_element_by_path("/TestPackage/TestCompuMethod").unwrap();
2899        assert_eq!(el_compu_method, el_compu_method_test);
2900
2901        // create more hierarchy
2902        let el_compu_internal_to_phys = el_compu_method
2903            .create_sub_element(ElementName::CompuInternalToPhys)
2904            .unwrap();
2905        let el_compu_scales = el_compu_internal_to_phys
2906            .create_sub_element(ElementName::CompuScales)
2907            .unwrap();
2908        let el_compu_scale = el_compu_scales.create_sub_element(ElementName::CompuScale).unwrap();
2909        el_compu_scale.create_sub_element(ElementName::Desc).unwrap();
2910
2911        // SHORT-LABEL should only be allowed before DESC inside COMPU-SCALE
2912        let (start_pos, end_pos) = el_compu_scale
2913            .calc_element_insert_range(ElementName::ShortLabel, AutosarVersion::Autosar_00050)
2914            .unwrap();
2915        assert_eq!(start_pos, 0);
2916        assert_eq!(end_pos, 0);
2917
2918        // COMPU-CONST should only be allowed after DESC inside COMPU-SCALE
2919        let (start_pos, end_pos) = el_compu_scale
2920            .calc_element_insert_range(ElementName::CompuConst, AutosarVersion::Autosar_00050)
2921            .unwrap();
2922        assert_eq!(start_pos, 1);
2923        assert_eq!(end_pos, 1);
2924
2925        // create COMPU-RATIONAL-COEFFS in COMPU-SCALE. It's presence excludes COMPU-CONST from being inserted
2926        el_compu_scale
2927            .create_sub_element(ElementName::CompuRationalCoeffs)
2928            .unwrap();
2929        // try to insert COMPU-CONST anyway
2930        let result = el_compu_scale.calc_element_insert_range(ElementName::CompuConst, AutosarVersion::Autosar_00050);
2931        assert!(result.is_err());
2932        // it is also not possible to create a second COMPU-RATIONAL-COEFFS
2933        let result =
2934            el_compu_scale.calc_element_insert_range(ElementName::CompuRationalCoeffs, AutosarVersion::Autosar_00050);
2935        assert!(result.is_err());
2936
2937        // creating a sub element at an invalid position fails
2938        assert!(
2939            el_elements
2940                .create_named_sub_element_at(ElementName::System, "System", 99)
2941                .is_err()
2942        );
2943        assert!(el_autosar.create_sub_element_at(ElementName::AdminData, 99).is_err());
2944
2945        // an identifiable element cannot be created without a name
2946        assert!(el_elements.create_sub_element(ElementName::System).is_err());
2947        // the name for an identifiable element must be valid according to the rules
2948        assert!(el_elements.create_named_sub_element(ElementName::System, "").is_err());
2949        assert!(
2950            el_elements
2951                .create_named_sub_element(ElementName::System, "abc def")
2952                .is_err()
2953        );
2954
2955        // a non-identifiable element cannot be created with a name
2956        assert!(
2957            el_autosar
2958                .create_named_sub_element(ElementName::AdminData, "AdminData")
2959                .is_err()
2960        );
2961
2962        // only valid sub-elements can be created
2963        assert!(
2964            el_autosar
2965                .create_named_sub_element(ElementName::Autosar, "Autosar")
2966                .is_err()
2967        );
2968        assert!(
2969            el_autosar
2970                .create_named_sub_element_at(ElementName::Autosar, "Autosar", 0)
2971                .is_err()
2972        );
2973        assert!(el_autosar.create_sub_element(ElementName::Autosar).is_err());
2974        assert!(el_autosar.create_sub_element_at(ElementName::Autosar, 0).is_err());
2975
2976        // creating a sub element fails when any parent element in the hierarchy is locked for writing
2977        let el_autosar_locked = el_autosar.0.write();
2978        assert!(
2979            el_elements
2980                .create_named_sub_element(ElementName::System, "System")
2981                .is_err()
2982        );
2983        assert!(
2984            el_elements
2985                .create_named_sub_element_at(ElementName::System, "System", 0)
2986                .is_err()
2987        );
2988        assert!(el_autosar.create_sub_element(ElementName::AdminData).is_err());
2989        assert!(el_autosar.create_sub_element_at(ElementName::AdminData, 0).is_err());
2990        drop(el_autosar_locked);
2991    }
2992
2993    #[test]
2994    fn element_creation_after_nonstrict_load() {
2995        // a non-strict load retains elements which are not valid in the version of the file:
2996        // PNC-VECTOR-LENGTH does not exist in Autosar 4.0.1
2997        let file_content = r#"<?xml version="1.0" encoding="utf-8"?>
2998        <AUTOSAR xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-0-1.xsd" xmlns="http://autosar.org/schema/r4.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
2999            <AR-PACKAGES><AR-PACKAGE><SHORT-NAME>Pkg</SHORT-NAME><ELEMENTS>
3000                <SYSTEM><SHORT-NAME>Sys</SHORT-NAME><PNC-VECTOR-LENGTH>8</PNC-VECTOR-LENGTH></SYSTEM>
3001            </ELEMENTS></AR-PACKAGE></AR-PACKAGES>
3002        </AUTOSAR>"#;
3003        let model = AutosarModel::new();
3004        let (_, warnings) = model
3005            .load_buffer(file_content.as_bytes(), OsString::from("test.arxml"), false)
3006            .unwrap();
3007        assert!(!warnings.is_empty());
3008
3009        // creating a new sub element must position it relative to the retained
3010        // version-incompatible sub element instead of panicking
3011        let el_system = model.get_element_by_path("/Pkg/Sys").unwrap();
3012        el_system.create_sub_element(ElementName::FibexElements).unwrap();
3013    }
3014
3015    #[test]
3016    fn parent() {
3017        let model = AutosarModel::new();
3018        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3019        let el_autosar = model.root_element();
3020        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3021        let el_ar_package = el_ar_packages
3022            .create_named_sub_element(ElementName::ArPackage, "Package")
3023            .unwrap();
3024        let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3025        let el_system = el_elements
3026            .create_named_sub_element(ElementName::System, "Sys")
3027            .unwrap();
3028        let el_fibex = el_system.create_sub_element(ElementName::FibexElements).unwrap();
3029        let el_fibex_cond = el_fibex
3030            .create_sub_element(ElementName::FibexElementRefConditional)
3031            .unwrap();
3032
3033        let parent = el_fibex_cond.parent().unwrap().unwrap();
3034        assert_eq!(parent, el_fibex);
3035        let named_parent = el_fibex_cond.named_parent().unwrap().unwrap();
3036        assert_eq!(named_parent, el_system);
3037
3038        let named_parent = el_system.named_parent().unwrap().unwrap();
3039        assert_eq!(named_parent, el_ar_package);
3040
3041        let named_parent = el_autosar.named_parent().unwrap();
3042        assert!(named_parent.is_none());
3043
3044        // trying to get the named parent of a removed element should fail
3045        el_autosar.remove_sub_element(el_ar_packages).unwrap();
3046        assert!(el_ar_package.named_parent().is_err());
3047    }
3048
3049    #[test]
3050    fn package() {
3051        let model = AutosarModel::new();
3052        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3053        let el_autosar = model.root_element();
3054        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3055        let el_ar_package = el_ar_packages
3056            .create_named_sub_element(ElementName::ArPackage, "Package")
3057            .unwrap();
3058        let el_ar_packages_2 = el_ar_package.create_sub_element(ElementName::ArPackages).unwrap();
3059        let el_ar_package_2 = el_ar_packages_2
3060            .create_named_sub_element(ElementName::ArPackage, "SubPackage")
3061            .unwrap();
3062
3063        assert_eq!(el_ar_package.package().unwrap(), None); // top level package has no parent package
3064        assert_eq!(el_ar_packages_2.package().unwrap().unwrap(), el_ar_package);
3065        assert_eq!(el_ar_package_2.package().unwrap().unwrap(), el_ar_package); // SubPackage -> Package
3066
3067        drop(el_autosar);
3068        drop(model);
3069        assert!(el_ar_package.package().is_err()); // the model is dropped, so the package cannot be accessed anymore
3070    }
3071
3072    #[test]
3073    fn element_rename() {
3074        let model = AutosarModel::new();
3075        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3076        let el_autosar = model.root_element();
3077        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3078        let el_ar_package = el_ar_packages
3079            .create_named_sub_element(ElementName::ArPackage, "Package")
3080            .unwrap();
3081        let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3082        let el_can_cluster = el_elements
3083            .create_named_sub_element_at(ElementName::CanCluster, "CanCluster", 0)
3084            .unwrap();
3085        let el_can_physical_channel = el_can_cluster
3086            .create_sub_element(ElementName::CanClusterVariants)
3087            .and_then(|ccv| ccv.create_sub_element(ElementName::CanClusterConditional))
3088            .and_then(|ccc| ccc.create_sub_element(ElementName::PhysicalChannels))
3089            .and_then(|pc| pc.create_named_sub_element(ElementName::CanPhysicalChannel, "CanPhysicalChannel"))
3090            .unwrap();
3091
3092        let el_can_frame_triggering = el_can_physical_channel
3093            .create_sub_element_at(ElementName::FrameTriggerings, 1)
3094            .and_then(|ft| ft.create_named_sub_element(ElementName::CanFrameTriggering, "CanFrameTriggering"))
3095            .unwrap();
3096
3097        let el_ar_package2 = el_ar_packages
3098            .create_named_sub_element(ElementName::ArPackage, "Package2")
3099            .unwrap();
3100        let el_can_frame = el_ar_package2
3101            .create_sub_element(ElementName::Elements)
3102            .and_then(|e| e.create_named_sub_element(ElementName::CanFrame, "CanFrame"))
3103            .unwrap();
3104        let el_frame_ref = el_can_frame_triggering
3105            .create_sub_element(ElementName::FrameRef)
3106            .unwrap();
3107        let _ = el_frame_ref.set_reference_target(&el_can_frame);
3108
3109        // initial value of the reference
3110        let refstr = el_frame_ref.character_data().unwrap().string_value().unwrap();
3111        assert_eq!(refstr, "/Package2/CanFrame");
3112
3113        // empty name, renaming should fail
3114        let result = el_ar_package.set_item_name("");
3115        assert!(result.is_err());
3116
3117        // rename 1. package
3118        el_ar_package.set_item_name("NewPackage").unwrap();
3119        // setting the current name again - should be a no-op
3120        el_ar_package.set_item_name("NewPackage").unwrap();
3121
3122        // duplicate name for Package2, renaming should fail
3123        let result = el_ar_package2.set_item_name("NewPackage");
3124        assert!(result.is_err());
3125
3126        // rename package 2 with a valid name
3127        el_ar_package2.set_item_name("OtherPackage").unwrap();
3128        let refstr = el_frame_ref.character_data().unwrap().string_value().unwrap();
3129        assert_eq!(refstr, "/OtherPackage/CanFrame");
3130
3131        // make sure get_reference_target still works after renaming
3132        let el_can_frame2 = el_frame_ref.get_reference_target().unwrap();
3133        assert_eq!(el_can_frame, el_can_frame2);
3134
3135        // rename the CanFrame as well
3136        el_can_frame.set_item_name("CanFrame_renamed").unwrap();
3137        let refstr = el_frame_ref.character_data().unwrap().string_value().unwrap();
3138        assert_eq!(refstr, "/OtherPackage/CanFrame_renamed");
3139
3140        // invalid element
3141        assert!(el_autosar.set_item_name("Autosar").is_err());
3142
3143        // invalid preconditions
3144        let el_autosar_locked = el_autosar.0.write();
3145        // fails because a parent element is locked
3146        assert!(el_ar_package.set_item_name("TestPackage_renamed").is_err());
3147        drop(el_autosar_locked);
3148        drop(model);
3149        // the reference count of model is now zero, so set_item_name can't get a new reference to it
3150        assert!(el_ar_package.set_item_name("TestPackage_renamed").is_err());
3151    }
3152
3153    #[test]
3154    fn element_copy() {
3155        let model = AutosarModel::new();
3156        model
3157            .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
3158            .unwrap();
3159        model.create_file("test", AutosarVersion::LATEST).unwrap();
3160        let el_ar_package = model.get_element_by_path("/TestPackage").unwrap();
3161        el_ar_package
3162            .set_attribute(AttributeName::Uuid, CharacterData::String("0123456".to_string()))
3163            .unwrap();
3164        let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3165        let el_compu_method = el_elements
3166            .create_named_sub_element(ElementName::CompuMethod, "CompuMethod")
3167            .unwrap();
3168        el_elements
3169            .create_named_sub_element(ElementName::DdsServiceInstanceToMachineMapping, "ApItem")
3170            .unwrap();
3171        el_elements
3172            .create_named_sub_element(ElementName::AclObjectSet, "AclObjectSet")
3173            .and_then(|el| el.create_sub_element(ElementName::DerivedFromBlueprintRefs))
3174            .and_then(|el| el.create_sub_element(ElementName::DerivedFromBlueprintRef))
3175            .and_then(|el| {
3176                el.set_attribute(
3177                    AttributeName::Dest,
3178                    CharacterData::Enum(EnumItem::AbstractImplementationDataType),
3179                )
3180            })
3181            .unwrap();
3182        el_elements
3183            .create_named_sub_element(ElementName::System, "System")
3184            .and_then(|el| el.create_sub_element(ElementName::FibexElements))
3185            .and_then(|el| el.create_sub_element(ElementName::FibexElementRefConditional))
3186            .and_then(|el| el.create_sub_element(ElementName::FibexElementRef))
3187            .and_then(|el| el.set_character_data("/invalid"))
3188            .unwrap();
3189
3190        let project2 = AutosarModel::new();
3191        project2
3192            .create_file("test.arxml", AutosarVersion::Autosar_00044)
3193            .unwrap();
3194
3195        // it should not be possible to create an AR-PACKAGE element directly in the AUTOSAR element by copying data
3196        let result = project2.root_element().create_copied_sub_element(&el_ar_package);
3197        assert!(result.is_err());
3198
3199        // create an AR-PACKAGES element and copy the data there. This should succeed.
3200        // the copied data shoud contain the COMPU-METHOD, but not the DDS-SERVICE-INSTANCE-TO-MACHINE-MAPPING
3201        // because the latter was specified in Adaptive 18-03 (Autosar_00045) and is not valid in Autosar_00044
3202        let el_ar_packages2 = project2
3203            .root_element()
3204            .create_sub_element(ElementName::ArPackages)
3205            .unwrap();
3206        el_ar_packages2.create_copied_sub_element(&el_ar_package).unwrap();
3207
3208        // it should be possible to look up the copied compu-method by its path
3209        let el_compu_method_2 = project2.get_element_by_path("/TestPackage/CompuMethod").unwrap();
3210
3211        // the copy should not refer to the same memory as the original
3212        assert_ne!(el_compu_method, el_compu_method_2);
3213        // the copy should serialize to exactly the same string as the original
3214        assert_eq!(el_compu_method.serialize(), el_compu_method_2.serialize());
3215
3216        // verify that the DDS-SERVICE-INSTANCE-TO-MACHINE-MAPPING element was not copied
3217        let result = project2.get_element_by_path("/TestPackage/ApItem");
3218        assert!(result.is_none());
3219
3220        // make sure the element ordering constraints are considered when copying with the _at() variant
3221        let el_ar_package2 = el_ar_packages2
3222            .create_named_sub_element(ElementName::ArPackage, "Package2")
3223            .unwrap();
3224        let result = el_ar_package2.create_copied_sub_element_at(&el_elements, 0);
3225        assert!(result.is_err()); // position 0 is already used by the SHORT-NAME
3226        let el_elements2 = el_ar_package2.create_sub_element(ElementName::Elements).unwrap();
3227        let result = el_elements2.create_copied_sub_element_at(&el_compu_method, 99);
3228        assert!(result.is_err()); // position 99 is not valid
3229        let result = el_elements2.create_copied_sub_element_at(&el_compu_method, 0);
3230        assert!(result.is_ok()); // position 0 is valid
3231
3232        // can't copy an element that is not a valid sub element here
3233        let result = el_ar_package2.create_copied_sub_element_at(&el_compu_method, 0);
3234        assert!(result.is_err()); // COMPU-METHOS id not a valid sub-element of AR-PACKAGE
3235    }
3236
3237    #[test]
3238    fn element_copy_loop() {
3239        let model = AutosarModel::new();
3240        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3241        let el_autosar = model.root_element();
3242        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3243        let el_ar_package = el_ar_packages
3244            .create_named_sub_element(ElementName::ArPackage, "Pkg")
3245            .unwrap();
3246
3247        let result = el_ar_package.create_copied_sub_element(&el_ar_packages);
3248        assert!(result.is_err());
3249
3250        // copying an element into itself should return an error and should not deadlock
3251        let result = el_ar_package.create_copied_sub_element(&el_ar_package);
3252        assert!(result.is_err());
3253        let result = el_ar_package.create_copied_sub_element_at(&el_ar_package, 0);
3254        assert!(result.is_err());
3255    }
3256
3257    #[test]
3258    fn element_self_reference_loop() {
3259        let model = AutosarModel::new();
3260        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3261        let el_autosar = model.root_element();
3262        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3263        let el_ar_package = el_ar_packages
3264            .create_named_sub_element(ElementName::ArPackage, "Pkg")
3265            .unwrap();
3266
3267        // removing an element from itself should return an error and should not deadlock
3268        let result = el_ar_package.remove_sub_element(el_ar_package.clone());
3269        assert!(matches!(result, Err(AutosarDataError::ElementNotFound { .. })));
3270
3271        // moving an element into itself should return an error and should not deadlock
3272        let result = el_ar_packages.move_element_here(&el_ar_packages.clone());
3273        assert!(matches!(result, Err(AutosarDataError::ForbiddenMoveToSubElement)));
3274        let result = el_ar_packages.move_element_here_at(&el_ar_packages.clone(), 0);
3275        assert!(matches!(result, Err(AutosarDataError::ForbiddenMoveToSubElement)));
3276    }
3277
3278    #[test]
3279    fn element_deletion() {
3280        let model = AutosarModel::new();
3281        model
3282            .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
3283            .unwrap();
3284        let el_ar_package = model.get_element_by_path("/TestPackage").unwrap();
3285        let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
3286        el_ar_package
3287            .create_sub_element(ElementName::Elements)
3288            .and_then(|el| el.create_named_sub_element(ElementName::System, "System"))
3289            .and_then(|el| el.create_sub_element(ElementName::FibexElements))
3290            .and_then(|el| el.create_sub_element(ElementName::FibexElementRefConditional))
3291            .and_then(|el| el.create_sub_element(ElementName::FibexElementRef))
3292            .and_then(|el| el.set_character_data("/invalid"))
3293            .unwrap();
3294
3295        // removing the SHORT-NAME of an identifiable element is forbidden
3296        let result = el_ar_package.remove_sub_element(el_short_name);
3297        if let Err(AutosarDataError::ShortNameRemovalForbidden) = result {
3298            // correct
3299        } else {
3300            panic!("Removing the SHORT-NAME was not prohibited");
3301        }
3302        let el_ar_package_clone = el_ar_package.clone();
3303        let el_ar_packages = el_ar_package.parent().unwrap().unwrap();
3304        let result = el_ar_packages.remove_sub_element(el_ar_package);
3305        // deleting identifiable elements should also cause the cached references to them to be removed
3306        assert_eq!(model.0.read().identifiables.len(), 0);
3307        assert!(result.is_ok());
3308
3309        // alternative: remove_sub_element_kind
3310        el_ar_packages
3311            .create_named_sub_element(ElementName::ArPackage, "SecondPackage")
3312            .unwrap();
3313        assert_eq!(el_ar_packages.content_item_count(), 1);
3314        let result = el_ar_packages.remove_sub_element_kind(ElementName::ArPackage);
3315        assert!(result.is_ok());
3316        assert_eq!(el_ar_packages.content_item_count(), 0);
3317        let result = el_ar_packages.remove_sub_element_kind(ElementName::ArPackage);
3318        assert!(result.is_err());
3319
3320        // the removed element may still exist if there were other references to it, but it is no longer usable
3321        let result = el_ar_package_clone.parent();
3322        assert!(matches!(result, Err(AutosarDataError::ItemDeleted)));
3323        let result = el_ar_package_clone.model();
3324        assert!(matches!(result, Err(AutosarDataError::ItemDeleted)));
3325        assert_eq!(el_ar_package_clone.position(), None);
3326    }
3327
3328    #[test]
3329    fn element_position() {
3330        let model = AutosarModel::new();
3331        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3332        let el_autosar = model.root_element();
3333        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3334        let el_ar_package1 = el_ar_packages
3335            .create_named_sub_element(ElementName::ArPackage, "Pkg1")
3336            .unwrap();
3337        let el_ar_package2 = el_ar_packages
3338            .create_named_sub_element(ElementName::ArPackage, "Pkg2")
3339            .unwrap();
3340        let el_ar_package3 = el_ar_packages
3341            .create_named_sub_element(ElementName::ArPackage, "Pkg3")
3342            .unwrap();
3343
3344        assert_eq!(el_ar_packages.content_item_count(), 3);
3345        assert_eq!(el_ar_package2.position().unwrap(), 1);
3346        assert_eq!(el_ar_packages.get_sub_element_at(1).unwrap(), el_ar_package2);
3347        assert_eq!(el_ar_package3.position().unwrap(), 2);
3348        assert_eq!(el_ar_packages.get_sub_element_at(2).unwrap(), el_ar_package3);
3349
3350        // there is no subelement at position 1
3351        let nonexistent = el_ar_package1.get_sub_element_at(1);
3352        assert_eq!(nonexistent, None);
3353    }
3354
3355    #[test]
3356    fn element_type() {
3357        let model = AutosarModel::new();
3358        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3359        let el_autosar = model.root_element();
3360
3361        assert_eq!(el_autosar.element_type(), ElementType::ROOT);
3362    }
3363
3364    #[test]
3365    fn content_type() {
3366        let model = AutosarModel::new();
3367        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3368        let el_autosar = model.root_element();
3369        let el_ar_package = el_autosar
3370            .create_sub_element(ElementName::ArPackages)
3371            .and_then(|ar_pkgs| ar_pkgs.create_named_sub_element(ElementName::ArPackage, "Package"))
3372            .unwrap();
3373        let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
3374
3375        let el_l4 = el_ar_package
3376            .create_sub_element(ElementName::LongName)
3377            .and_then(|ln| ln.create_sub_element(ElementName::L4))
3378            .unwrap();
3379
3380        let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3381        let el_debounce_algo = el_elements
3382            .create_named_sub_element(ElementName::DiagnosticContributionSet, "DCS")
3383            .and_then(|dcs| dcs.create_sub_element(ElementName::CommonProperties))
3384            .and_then(|cp| cp.create_sub_element(ElementName::DiagnosticCommonPropsVariants))
3385            .and_then(|dcpv| dcpv.create_sub_element(ElementName::DiagnosticCommonPropsConditional))
3386            .and_then(|dcpc| dcpc.create_sub_element(ElementName::DebounceAlgorithmPropss))
3387            .and_then(|dap| dap.create_named_sub_element(ElementName::DiagnosticDebounceAlgorithmProps, "ddap"))
3388            .and_then(|ddap| ddap.create_sub_element(ElementName::DebounceAlgorithm))
3389            .unwrap();
3390
3391        assert_eq!(el_autosar.element_type().content_mode(), ContentMode::Sequence);
3392        assert_eq!(el_autosar.content_type(), ContentType::Elements);
3393        assert_eq!(el_elements.element_type().content_mode(), ContentMode::Bag);
3394        assert_eq!(el_elements.content_type(), ContentType::Elements);
3395        assert_eq!(el_debounce_algo.element_type().content_mode(), ContentMode::Choice);
3396        assert_eq!(el_debounce_algo.content_type(), ContentType::Elements);
3397        assert_eq!(el_short_name.element_type().content_mode(), ContentMode::Characters);
3398        assert_eq!(el_short_name.content_type(), ContentType::CharacterData);
3399        assert_eq!(el_l4.element_type().content_mode(), ContentMode::Mixed);
3400        assert_eq!(el_l4.content_type(), ContentType::Mixed);
3401    }
3402
3403    #[test]
3404    fn attributes() {
3405        let model = AutosarModel::new();
3406        model
3407            .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
3408            .unwrap();
3409        model.create_file("test", AutosarVersion::LATEST).unwrap();
3410        let el_autosar = model.root_element();
3411        let el_ar_packages = el_autosar.get_sub_element(ElementName::ArPackages).unwrap();
3412
3413        let count = el_autosar.attributes().count();
3414        assert_eq!(count, 3);
3415
3416        // set the attribute S on the element AUTOSAR
3417        el_autosar
3418            .set_attribute(AttributeName::S, CharacterData::String(String::from("something")))
3419            .unwrap();
3420
3421        // AUTOSAR has no DEST attribute, so this should fail
3422        assert!(
3423            el_autosar
3424                .set_attribute(AttributeName::Dest, CharacterData::String(String::from("something")))
3425                .is_err()
3426        );
3427
3428        // The attribute S exists and is optional, so it can be removed
3429        let result = el_autosar.remove_attribute(AttributeName::S);
3430        assert!(result);
3431
3432        // the attribute xmlns is required and cannot be removed
3433        let result = el_autosar.remove_attribute(AttributeName::xmlns);
3434        assert!(!result);
3435
3436        // the attribute ACCESSKEY does not exist in the element AUTOSAR and cannot be removed
3437        let result = el_autosar.remove_attribute(AttributeName::Accesskey);
3438        assert!(!result);
3439
3440        // the attribute T is permitted on AUTOSAR and the string is a valid value
3441        el_autosar
3442            .set_attribute_string(AttributeName::T, "2022-01-31T13:00:59Z")
3443            .unwrap();
3444
3445        // update an existing attribute
3446        el_autosar
3447            .set_attribute_string(AttributeName::T, "2022-01-31T14:00:59Z")
3448            .unwrap();
3449
3450        // fail set an attribute due to data validation
3451        assert!(el_autosar.set_attribute_string(AttributeName::T, "abc").is_err());
3452
3453        // can't set unknown attributes with set_attribute_string
3454        assert!(
3455            el_ar_packages
3456                .set_attribute_string(AttributeName::xmlns, "abc")
3457                .is_err()
3458        );
3459
3460        // directly return an attribute as a string
3461        let xmlns = el_autosar
3462            .attribute_value(AttributeName::xmlns)
3463            .map(|cdata| cdata.to_string())
3464            .unwrap();
3465        assert_eq!(xmlns, "http://autosar.org/schema/r4.0".to_string());
3466
3467        // attribute operation fails when a parent element is locked for writing
3468        let lock = el_autosar.0.write();
3469        assert!(
3470            el_ar_packages
3471                .set_attribute(AttributeName::Uuid, CharacterData::String(String::from("1234")))
3472                .is_err()
3473        );
3474        assert!(
3475            el_ar_packages
3476                .set_attribute_string(AttributeName::Uuid, "1234")
3477                .is_err()
3478        );
3479        drop(lock);
3480    }
3481
3482    #[test]
3483    fn mixed_content() {
3484        let model = AutosarModel::new();
3485        model
3486            .load_buffer(BASIC_AUTOSAR_FILE.as_bytes(), OsString::from("test.arxml"), true)
3487            .unwrap();
3488        let el_ar_package = model.get_element_by_path("/TestPackage").unwrap();
3489        let el_long_name = el_ar_package.create_sub_element(ElementName::LongName).unwrap();
3490        assert_eq!(el_long_name.content_type(), ContentType::Elements);
3491        let el_l_4 = el_long_name.create_sub_element(ElementName::L4).unwrap();
3492        assert_eq!(el_l_4.content_type(), ContentType::Mixed);
3493
3494        el_l_4.create_sub_element(ElementName::E).unwrap();
3495        el_l_4.insert_character_content_item("foo", 1).unwrap();
3496        el_l_4.create_sub_element(ElementName::Sup).unwrap();
3497        el_l_4.insert_character_content_item("bar", 0).unwrap();
3498        assert_eq!(el_l_4.content().count(), 4);
3499
3500        // character data item "foo" is now in position 2 and gets removed
3501        assert!(el_l_4.remove_character_content_item(2).is_ok());
3502        assert_eq!(el_l_4.content().count(), 3);
3503        // character data item "bar" should be in postion 0
3504        let item = el_l_4.content().next().unwrap();
3505        if let ElementContent::CharacterData(CharacterData::String(content)) = item {
3506            assert_eq!(content, "bar");
3507        } else {
3508            panic!("unexpected content in <L-4>: {item:?}");
3509        }
3510    }
3511
3512    #[test]
3513    fn move_element_position() {
3514        // move an element to a different position within its parent
3515        let model = AutosarModel::new();
3516        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3517        let el_autosar = model.root_element();
3518        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3519        let pkg1 = el_ar_packages
3520            .create_named_sub_element(ElementName::ArPackage, "Pkg1")
3521            .unwrap();
3522        let pkg2 = el_ar_packages
3523            .create_named_sub_element(ElementName::ArPackage, "Pkg2")
3524            .unwrap();
3525        let pkg3 = el_ar_packages
3526            .create_named_sub_element(ElementName::ArPackage, "Pkg3")
3527            .unwrap();
3528
3529        // "moving" an element inside its parent without actually giving a position is a no-op
3530        el_ar_packages.move_element_here(&pkg3).unwrap();
3531
3532        // moving to an invalid position fails
3533        assert!(el_ar_packages.move_element_here_at(&pkg1, 99).is_err());
3534        assert!(el_ar_packages.move_element_here_at(&pkg1, 3).is_err()); // special boundary case
3535
3536        // move an element forward
3537        el_ar_packages.move_element_here_at(&pkg2, 0).unwrap();
3538        // move an element backward
3539        el_ar_packages.move_element_here_at(&pkg1, 2).unwrap();
3540        // check the new ordering
3541        let mut packages_iter = el_ar_packages.sub_elements();
3542        assert_eq!(packages_iter.next().unwrap(), pkg2);
3543        assert_eq!(packages_iter.next().unwrap(), pkg3);
3544        assert_eq!(packages_iter.next().unwrap(), pkg1);
3545
3546        // moving elements should also work with mixed content
3547        let el_l_4 = pkg1
3548            .create_sub_element(ElementName::LongName)
3549            .and_then(|el| el.create_sub_element(ElementName::L4))
3550            .unwrap();
3551        el_l_4.create_sub_element(ElementName::E).unwrap();
3552        el_l_4.insert_character_content_item("foo", 1).unwrap();
3553        let el_sup = el_l_4.create_sub_element(ElementName::Sup).unwrap();
3554        el_l_4.insert_character_content_item("bar", 0).unwrap();
3555        el_l_4.move_element_here_at(&el_sup, 0).unwrap();
3556        let mut iter = el_l_4.sub_elements();
3557        assert_eq!(iter.next().unwrap(), el_sup);
3558    }
3559
3560    #[test]
3561    fn move_element_local() {
3562        // move an element within the same model
3563        let model = AutosarModel::new();
3564        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3565        let el_autosar = model.root_element();
3566        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3567        let el_pkg1 = el_ar_packages
3568            .create_named_sub_element(ElementName::ArPackage, "Pkg1")
3569            .unwrap();
3570        let el_elements1 = el_pkg1.create_sub_element(ElementName::Elements).unwrap();
3571        let el_ecu_instance = el_elements1
3572            .create_named_sub_element(ElementName::EcuInstance, "EcuInstance")
3573            .unwrap();
3574        let el_pkg2 = el_ar_packages
3575            .create_named_sub_element(ElementName::ArPackage, "Pkg2")
3576            .unwrap();
3577        let el_pkg3 = el_ar_packages
3578            .create_named_sub_element(ElementName::ArPackage, "Pkg3")
3579            .unwrap();
3580        let el_fibex_element_ref = el_pkg3
3581            .create_sub_element(ElementName::Elements)
3582            .and_then(|el| el.create_named_sub_element(ElementName::System, "System"))
3583            .and_then(|el| el.create_sub_element(ElementName::FibexElements))
3584            .and_then(|el| el.create_sub_element(ElementName::FibexElementRefConditional))
3585            .and_then(|el| el.create_sub_element(ElementName::FibexElementRef))
3586            .unwrap();
3587        el_fibex_element_ref.set_reference_target(&el_ecu_instance).unwrap();
3588
3589        // can't move an element of the wrong type
3590        assert!(el_ar_packages.move_element_here(&el_autosar).is_err());
3591        assert!(el_ar_packages.move_element_here_at(&el_autosar, 0).is_err());
3592
3593        // moving an element into its own sub element (creating a loop) is forbidden
3594        assert!(el_pkg1.move_element_here(&el_ar_packages).is_err());
3595        assert!(el_pkg1.move_element_here_at(&el_ar_packages, 1).is_err());
3596
3597        // move an unnamed element
3598        assert!(model.get_element_by_path("/Pkg1/EcuInstance").is_some());
3599        el_pkg2.move_element_here(&el_elements1).unwrap();
3600        assert_eq!(el_elements1.parent().unwrap().unwrap(), el_pkg2);
3601        assert!(model.get_element_by_path("/Pkg2/EcuInstance").is_some());
3602        assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3603
3604        // move the unnamed element back using the _at variant
3605        el_pkg1.move_element_here_at(&el_elements1, 1).unwrap();
3606        assert_eq!(el_elements1.parent().unwrap().unwrap(), el_pkg1);
3607        assert!(model.get_element_by_path("/Pkg1/EcuInstance").is_some());
3608        assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3609
3610        // move a named element
3611        let el_elements2 = el_pkg2.create_sub_element(ElementName::Elements).unwrap();
3612        el_elements2.move_element_here(&el_ecu_instance).unwrap();
3613        assert_eq!(el_ecu_instance.parent().unwrap().unwrap(), el_elements2);
3614        assert!(model.get_element_by_path("/Pkg2/EcuInstance").is_some());
3615        assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3616
3617        // moving an element should automatically resolve name conflicts
3618        el_elements1
3619            .create_named_sub_element(ElementName::EcuInstance, "EcuInstance")
3620            .unwrap();
3621        el_elements1.move_element_here_at(&el_ecu_instance, 0).unwrap();
3622        assert_eq!(el_ecu_instance.parent().unwrap().unwrap(), el_elements1);
3623        assert!(model.get_element_by_path("/Pkg1/EcuInstance_1").is_some());
3624        assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3625    }
3626
3627    #[test]
3628    fn move_element_full() {
3629        // move an element between two projects
3630        let model1 = AutosarModel::new();
3631        model1
3632            .create_file("test1.arxml", AutosarVersion::Autosar_00050)
3633            .unwrap();
3634        let el_autosar = model1.root_element();
3635        let el_ar_packages1 = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
3636        let el_pkg1 = el_ar_packages1
3637            .create_named_sub_element(ElementName::ArPackage, "Pkg1")
3638            .unwrap();
3639        let el_elements1 = el_pkg1.create_sub_element(ElementName::Elements).unwrap();
3640        let el_ecu_instance = el_elements1
3641            .create_named_sub_element(ElementName::EcuInstance, "EcuInstance")
3642            .unwrap();
3643        let el_pkg2 = el_ar_packages1
3644            .create_named_sub_element(ElementName::ArPackage, "Pkg2")
3645            .unwrap();
3646        let el_fibex_element_ref = el_pkg2
3647            .create_sub_element(ElementName::Elements)
3648            .and_then(|el| el.create_named_sub_element(ElementName::System, "System"))
3649            .and_then(|el| el.create_sub_element(ElementName::FibexElements))
3650            .and_then(|el| el.create_sub_element(ElementName::FibexElementRefConditional))
3651            .and_then(|el| el.create_sub_element(ElementName::FibexElementRef))
3652            .unwrap();
3653        el_fibex_element_ref.set_reference_target(&el_ecu_instance).unwrap();
3654
3655        let model2 = AutosarModel::new();
3656        model2
3657            .create_file("test2.arxml", AutosarVersion::Autosar_00050)
3658            .unwrap();
3659        let el_autosar2 = model2.root_element();
3660        let el_ar_packages2 = el_autosar2.create_sub_element(ElementName::ArPackages).unwrap();
3661
3662        // move a named element
3663        el_ar_packages2.move_element_here(&el_pkg1).unwrap();
3664        assert!(model1.get_element_by_path("/Pkg1").is_none());
3665        assert!(model2.get_element_by_path("/Pkg1").is_some());
3666        el_ar_packages2.move_element_here_at(&el_pkg2, 1).unwrap();
3667        assert!(model1.get_element_by_path("/Pkg2").is_none());
3668        assert!(model2.get_element_by_path("/Pkg2").is_some());
3669
3670        // move an unnamed element
3671        el_autosar.remove_sub_element(el_ar_packages1).unwrap();
3672        el_autosar.move_element_here(&el_ar_packages2).unwrap();
3673        assert!(model1.get_element_by_path("/Pkg1/EcuInstance").is_some());
3674        assert!(model1.get_element_by_path("/Pkg2/System").is_some());
3675        assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance);
3676
3677        // can't move an element when one of the projects is deleted
3678        drop(model2);
3679        assert!(el_autosar2.move_element_here(&el_ar_packages2).is_err());
3680        assert!(el_autosar2.move_element_here_at(&el_ar_packages2, 0).is_err());
3681
3682        // can't move between files with different versions
3683        let project3 = AutosarModel::new();
3684        project3
3685            .create_file("test2.arxml", AutosarVersion::Autosar_4_3_0)
3686            .unwrap();
3687        let el_autosar3 = project3.root_element();
3688        assert!(el_autosar3.move_element_here(&el_ar_packages2).is_err());
3689        assert!(el_autosar3.move_element_here_at(&el_ar_packages2, 0).is_err());
3690    }
3691
3692    #[test]
3693    fn get_set_reference_target() {
3694        let model = AutosarModel::new();
3695        model.create_file("text.arxml", AutosarVersion::Autosar_00050).unwrap();
3696        let el_autosar = model.root_element();
3697        let el_ar_package = el_autosar
3698            .create_sub_element(ElementName::ArPackages)
3699            .and_then(|arpkgs| arpkgs.create_named_sub_element(ElementName::ArPackage, "Package"))
3700            .unwrap();
3701        let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3702        let el_ecu_instance1 = el_elements
3703            .create_named_sub_element(ElementName::EcuInstance, "EcuInstance1")
3704            .unwrap();
3705        let el_ecu_instance2 = el_elements
3706            .create_named_sub_element(ElementName::EcuInstance, "EcuInstance2")
3707            .unwrap();
3708        let el_req_result = el_elements
3709            .create_named_sub_element(ElementName::DiagnosticRoutine, "DiagRoutine")
3710            .and_then(|dr| dr.create_named_sub_element(ElementName::RequestResult, "RequestResult"))
3711            .unwrap();
3712        let el_fibex_element_ref = el_elements
3713            .create_named_sub_element(ElementName::System, "System")
3714            .and_then(|sys| sys.create_sub_element(ElementName::FibexElements))
3715            .and_then(|fe| fe.create_sub_element(ElementName::FibexElementRefConditional))
3716            .and_then(|ferc| ferc.create_sub_element(ElementName::FibexElementRef))
3717            .unwrap();
3718        let el_physical_request_ref = el_elements
3719            .create_named_sub_element(ElementName::DiagnosticConnection, "DiagnosticConnection")
3720            .and_then(|dc| dc.create_sub_element(ElementName::PhysicalRequestRef))
3721            .unwrap();
3722        let el_connection_ident = el_elements
3723            .create_named_sub_element(ElementName::CanTpConfig, "CanTpConfig")
3724            .and_then(|ctc| ctc.create_sub_element(ElementName::TpConnections))
3725            .and_then(|tc: Element| tc.create_sub_element(ElementName::CanTpConnection))
3726            .and_then(|ctc: Element| ctc.create_named_sub_element(ElementName::Ident, "ConnectionIdent"))
3727            .unwrap();
3728
3729        // set_reference_target does not work for elements which are not references
3730        assert!(el_elements.set_reference_target(&el_ar_package).is_err());
3731        // element AUTOSAR is not identifiable, and a reference to it cannot be set
3732        assert!(el_fibex_element_ref.set_reference_target(&el_autosar).is_err());
3733        // element AR-PACKAGE is identifiable, but not a valid reference target for a FIBEX-ELEMENT-REF
3734        assert!(el_fibex_element_ref.set_reference_target(&el_ar_package).is_err());
3735        // element REQUEST-RESULT is identifiable, but cannot be referenced by any other element as here is no valid DEST enum entry for it
3736        assert!(el_fibex_element_ref.set_reference_target(&el_req_result).is_err());
3737
3738        // set a valid reference and verify that the reference can be used
3739        el_fibex_element_ref.set_reference_target(&el_ecu_instance1).unwrap();
3740        assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance1);
3741        // update with a different valid reference and verify that the reference can be used
3742        el_fibex_element_ref.set_reference_target(&el_ecu_instance2).unwrap();
3743        assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance2);
3744
3745        // define a REFERENCE-BASE and then set a relative reference via BASE
3746        let el_reference_bases = el_ar_package.create_sub_element(ElementName::ReferenceBases).unwrap();
3747        let el_reference_base = el_reference_bases
3748            .create_sub_element(ElementName::ReferenceBase)
3749            .unwrap();
3750        el_reference_base
3751            .create_sub_element(ElementName::ShortLabel)
3752            .and_then(|short_label| short_label.set_character_data("default"))
3753            .unwrap();
3754        el_reference_base
3755            .create_sub_element(ElementName::PackageRef)
3756            .and_then(|package_ref| package_ref.set_reference_target(&el_ar_package))
3757            .unwrap();
3758        assert_eq!(
3759            el_reference_base.resolve_reference_base("default").as_deref(),
3760            Some("/Package")
3761        );
3762
3763        el_fibex_element_ref
3764            .set_relative_reference_target(&el_ecu_instance2, "default")
3765            .unwrap();
3766        assert_eq!(
3767            el_fibex_element_ref
3768                .attribute_value(AttributeName::Base)
3769                .and_then(|cdata| cdata.string_value())
3770                .unwrap(),
3771            "default"
3772        );
3773        assert_eq!(
3774            el_fibex_element_ref
3775                .character_data()
3776                .and_then(|cdata| cdata.string_value())
3777                .unwrap(),
3778            "EcuInstance2"
3779        );
3780        assert_eq!(el_fibex_element_ref.get_reference_target().unwrap(), el_ecu_instance2);
3781        assert!(
3782            model
3783                .get_references_to("/Package/EcuInstance2")
3784                .contains(&el_fibex_element_ref.downgrade())
3785        );
3786
3787        // set a valid reference to <CAN-TP-CONNECTION><IDENT>.
3788        // This is a complex case, as the correct DEST attribute must be looked up in the specification
3789        el_physical_request_ref
3790            .set_reference_target(&el_connection_ident)
3791            .unwrap();
3792        assert_eq!(
3793            el_physical_request_ref.get_reference_target().unwrap(),
3794            el_connection_ident
3795        );
3796
3797        // invalid reference: bad DEST attribute
3798        el_fibex_element_ref
3799            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
3800            .unwrap();
3801        assert!(el_fibex_element_ref.get_reference_target().is_err());
3802        // everything up to this point went through the public API, so the caches must be consistent
3803        assert_eq!(model.verify_reference_caches(), Ok(()));
3804        // invalid reference: no DEST attribute.
3805        // DEST is a required attribute, so remove_attribute() refuses to remove it and the test has to
3806        // reach into the element instead. This also drops the BASE attribute, which desynchronizes the
3807        // reference caches by design - so verify_reference_caches() must not be called after this.
3808        el_fibex_element_ref.0.write().attributes.clear(); // remove the DEST attribute
3809        assert!(el_fibex_element_ref.get_reference_target().is_err());
3810        el_fibex_element_ref.set_reference_target(&el_ecu_instance2).unwrap();
3811        // invalid reference: bad reference string
3812        el_fibex_element_ref
3813            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::EcuInstance))
3814            .unwrap();
3815        el_fibex_element_ref.set_character_data("/does/not/exist").unwrap();
3816        assert!(el_fibex_element_ref.get_reference_target().is_err());
3817        // invalid reference: refers to the wrong type of element
3818        el_fibex_element_ref.set_character_data("/Package").unwrap();
3819        assert!(el_fibex_element_ref.get_reference_target().is_err());
3820        // invalid reference: no reference string
3821        el_fibex_element_ref.remove_character_data().unwrap();
3822        assert!(el_fibex_element_ref.get_reference_target().is_err());
3823        el_fibex_element_ref.set_reference_target(&el_ecu_instance2).unwrap();
3824        // not a reference
3825        assert!(el_elements.get_reference_target().is_err());
3826        // model is deleted
3827        drop(model);
3828        assert!(el_fibex_element_ref.get_reference_target().is_err());
3829    }
3830
3831    #[test]
3832    fn relative_reference_outside_package() {
3833        let model = AutosarModel::new();
3834        model.create_file("test.arxml", AutosarVersion::Autosar_00050).unwrap();
3835        let el_autosar = model.root_element();
3836        let el_ar_package = el_autosar
3837            .create_sub_element(ElementName::ArPackages)
3838            .and_then(|arpkgs| arpkgs.create_named_sub_element(ElementName::ArPackage, "Package"))
3839            .unwrap();
3840
3841        // create a reference element that is not inside any package: AUTOSAR > ADMIN-DATA > SDGS > SDG > SDX-REF
3842        let el_sdx_ref = el_autosar
3843            .create_sub_element(ElementName::AdminData)
3844            .and_then(|admin_data| admin_data.create_sub_element(ElementName::Sdgs))
3845            .and_then(|sdgs| sdgs.create_sub_element(ElementName::Sdg))
3846            .and_then(|sdg| sdg.create_sub_element(ElementName::SdxRef))
3847            .unwrap();
3848        assert!(el_sdx_ref.is_reference());
3849        assert!(el_sdx_ref.package().unwrap().is_none());
3850
3851        // setting a relative reference target must fail (not panic), since there is no
3852        // containing package against which the base label could be resolved
3853        let result = el_sdx_ref.set_relative_reference_target(&el_ar_package, "default");
3854        assert!(matches!(result, Err(AutosarDataError::InvalidReferenceBase)));
3855
3856        // getting the reference target of a relative reference outside a package must also fail instead of panicking
3857        el_sdx_ref.set_character_data("Package").unwrap();
3858        el_sdx_ref
3859            .set_attribute(AttributeName::Base, CharacterData::String("default".to_string()))
3860            .unwrap();
3861        let result = el_sdx_ref.get_reference_target();
3862        assert!(matches!(result, Err(AutosarDataError::InvalidReference)));
3863        assert_eq!(model.verify_reference_caches(), Ok(()));
3864    }
3865
3866    #[test]
3867    fn modify_character_data() {
3868        let model = AutosarModel::new();
3869        model.create_file("text.arxml", AutosarVersion::Autosar_00050).unwrap();
3870        let el_autosar = model.root_element();
3871        let el_ar_package = el_autosar
3872            .create_sub_element(ElementName::ArPackages)
3873            .and_then(|arpkgs| arpkgs.create_named_sub_element(ElementName::ArPackage, "Package"))
3874            .unwrap();
3875        let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
3876        let el_elements = el_ar_package.create_sub_element(ElementName::Elements).unwrap();
3877        let el_system = el_elements
3878            .create_named_sub_element(ElementName::System, "System")
3879            .unwrap();
3880        let el_fibex_element_ref = el_system
3881            .create_sub_element(ElementName::FibexElements)
3882            .and_then(|fe| fe.create_sub_element(ElementName::FibexElementRefConditional))
3883            .and_then(|ferc| ferc.create_sub_element(ElementName::FibexElementRef))
3884            .unwrap();
3885        let el_pnc_vector_length = el_system.create_sub_element(ElementName::PncVectorLength).unwrap();
3886
3887        // set character data on an "ordinary" element that has no special handling
3888        assert!(
3889            el_pnc_vector_length
3890                .set_character_data(CharacterData::String("2".to_string()))
3891                .is_ok()
3892        ); // "native" type is String, without automatic wrapping
3893        assert!(el_pnc_vector_length.set_character_data("2".to_string()).is_ok()); // "native" type is String
3894        assert!(el_pnc_vector_length.set_character_data("2").is_ok()); // automatic conversion: &str -> String
3895        assert!(el_pnc_vector_length.set_character_data(2).is_ok()); // automatic conversion: u64 -> String
3896
3897        // set a new SHORT-NAME, this also updates path cache
3898        assert!(
3899            el_short_name
3900                .set_character_data(CharacterData::String("PackageRenamed".to_string()))
3901                .is_ok()
3902        );
3903        assert_eq!(
3904            el_short_name.character_data().unwrap().string_value().unwrap(),
3905            "PackageRenamed"
3906        );
3907        model.get_element_by_path("/PackageRenamed").unwrap();
3908
3909        // set a new reference target, which creates an entry in the reference origin cache
3910        assert!(
3911            el_fibex_element_ref
3912                .set_character_data("/PackageRenamed/EcuInstance1")
3913                .is_ok()
3914        );
3915        model
3916            .0
3917            .read()
3918            .reference_origins
3919            .get("/PackageRenamed/EcuInstance1")
3920            .unwrap();
3921
3922        // modify the reference target, which updates the entry in the reference origin cache
3923        assert!(
3924            el_fibex_element_ref
3925                .set_character_data("/PackageRenamed/EcuInstance2")
3926                .is_ok()
3927        );
3928        model
3929            .0
3930            .read()
3931            .reference_origins
3932            .get("/PackageRenamed/EcuInstance2")
3933            .unwrap();
3934        assert!(
3935            !model
3936                .0
3937                .read()
3938                .reference_origins
3939                .contains_key("/PackageRenamed/EcuInstance1")
3940        );
3941
3942        // can only set character data that are specified with ContentMode::Characters
3943        assert!(el_autosar.set_character_data("text").is_err());
3944
3945        // can't set a value that doesn't match the target spec
3946        assert!(el_short_name.set_character_data(0).is_err());
3947        assert!(el_short_name.set_character_data("").is_err());
3948
3949        // remove character data
3950        assert!(el_pnc_vector_length.remove_character_data().is_ok());
3951
3952        // remove the character data of a reference
3953        assert!(el_fibex_element_ref.remove_character_data().is_ok());
3954        assert!(
3955            !model
3956                .0
3957                .read()
3958                .reference_origins
3959                .contains_key("/PackageRenamed/EcuInstance2")
3960        );
3961
3962        // remove on an element whose character data has already been removed is not an error
3963        assert!(el_fibex_element_ref.remove_character_data().is_ok());
3964
3965        // can't remove SHORT-NAME
3966        assert!(el_short_name.remove_character_data().is_err());
3967
3968        // can't remove from elements which do not contain character data
3969        assert!(el_autosar.remove_character_data().is_err());
3970
3971        // slightly different behavior for the internal version that is used for locked elements
3972        assert!(
3973            el_autosar
3974                .0
3975                .write()
3976                .set_character_data(0, AutosarVersion::Autosar_00050)
3977                .is_err()
3978        );
3979        assert!(
3980            el_fibex_element_ref
3981                .0
3982                .write()
3983                .set_character_data(0, AutosarVersion::Autosar_00050)
3984                .is_err()
3985        );
3986
3987        // operation fails if the model is needed (e.g. reference or short name update), but the model has been deleted
3988        el_fibex_element_ref
3989            .set_character_data("/PackageRenamed/EcuInstance2")
3990            .unwrap();
3991        drop(model);
3992        assert!(
3993            el_fibex_element_ref
3994                .set_character_data("/PackageRenamed/EcuInstance1")
3995                .is_err()
3996        );
3997        assert!(el_fibex_element_ref.remove_character_data().is_err());
3998    }
3999
4000    #[test]
4001    fn mixed_character_content() {
4002        let model = AutosarModel::new();
4003        model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
4004        let el_ar_package = model
4005            .root_element()
4006            .create_sub_element(ElementName::ArPackages)
4007            .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
4008            .unwrap();
4009        let el_desc = el_ar_package.create_sub_element(ElementName::Desc).unwrap();
4010        let el_l2 = el_desc.create_sub_element(ElementName::L2).unwrap();
4011
4012        // ok: add a character content item to a vaild element at a valid position
4013        el_l2.insert_character_content_item("descriptive text", 0).unwrap();
4014
4015        // ok: add an element to the mixed item as well
4016        el_l2.create_sub_element(ElementName::Br).unwrap();
4017
4018        // not ok: add a character content item to a valid element at an invalid position
4019        assert!(el_l2.insert_character_content_item("more text", 99).is_err());
4020
4021        // not ok: add a character content item to an invalid element
4022        assert!(el_desc.insert_character_content_item("text", 0).is_err());
4023
4024        // not ok: remove character content from an invalid position
4025        assert!(el_l2.remove_character_content_item(99).is_err());
4026
4027        // not ok: remove character content from an invalid element
4028        assert!(el_desc.remove_character_content_item(0).is_err());
4029
4030        // not ok: remove a sub-element
4031        assert!(el_l2.remove_character_content_item(1).is_err());
4032
4033        // ok: remove character content from a valid element at a valid position
4034        el_l2.remove_character_content_item(0).unwrap();
4035    }
4036
4037    #[test]
4038    fn get_sub_element() {
4039        let model = AutosarModel::new();
4040        model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
4041        let el_autosar = model.root_element();
4042        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4043        let el_ar_package = el_ar_packages
4044            .create_named_sub_element(ElementName::ArPackage, "Package")
4045            .unwrap();
4046        let el_desc = el_ar_package.create_sub_element(ElementName::Desc).unwrap();
4047        let el_l2 = el_desc.create_sub_element(ElementName::L2).unwrap();
4048
4049        el_l2.insert_character_content_item("descriptive text", 0).unwrap();
4050        el_l2.create_sub_element(ElementName::Br).unwrap();
4051
4052        assert_eq!(
4053            el_autosar.get_sub_element(ElementName::ArPackages).unwrap(),
4054            el_ar_packages
4055        );
4056        assert!(el_autosar.get_sub_element(ElementName::Abs).is_none());
4057        assert!(el_l2.get_sub_element(ElementName::Br).is_some());
4058    }
4059
4060    #[test]
4061    fn get_or_create() {
4062        let model = AutosarModel::new();
4063        model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
4064        let el_autosar = model.root_element();
4065
4066        assert_eq!(el_autosar.sub_elements().count(), 0);
4067        let el_admin_data = el_autosar.get_or_create_sub_element(ElementName::AdminData).unwrap();
4068        let el_ar_packages = el_autosar.get_or_create_sub_element(ElementName::ArPackages).unwrap();
4069        let el_ar_packages2 = el_autosar.get_or_create_sub_element(ElementName::ArPackages).unwrap();
4070        assert_ne!(el_admin_data, el_ar_packages);
4071        assert_eq!(el_ar_packages, el_ar_packages2);
4072
4073        let el_ar_package = el_ar_packages
4074            .get_or_create_named_sub_element(ElementName::ArPackage, "Pkg")
4075            .unwrap();
4076        let el_ar_package2 = el_ar_packages
4077            .get_or_create_named_sub_element(ElementName::ArPackage, "Pkg2")
4078            .unwrap();
4079        let el_ar_package3 = el_ar_packages
4080            .get_or_create_named_sub_element(ElementName::ArPackage, "Pkg2")
4081            .unwrap();
4082        assert_ne!(el_ar_package, el_ar_package2);
4083        assert_eq!(el_ar_package2, el_ar_package3);
4084    }
4085
4086    #[test]
4087    fn serialize() {
4088        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
4089<!--comment-->
4090<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">
4091  <AR-PACKAGES>
4092    <AR-PACKAGE>
4093      <SHORT-NAME>Pkg</SHORT-NAME>
4094      <DESC>
4095        <L-2 L="EN">Description<BR/>Description</L-2>
4096      </DESC>
4097    </AR-PACKAGE>
4098  </AR-PACKAGES>
4099</AUTOSAR>"#;
4100        let model = AutosarModel::new();
4101        model
4102            .load_buffer(FILEBUF.as_bytes(), OsString::from("test"), true)
4103            .unwrap();
4104        model.files().next().unwrap();
4105        let el_autosar = model.root_element();
4106        el_autosar.set_comment(Some("comment".to_string()));
4107
4108        let mut outstring = String::from(r#"<?xml version="1.0" encoding="utf-8"?>"#);
4109        el_autosar
4110            .0
4111            .read()
4112            .serialize_internal(&mut outstring, 0, false, &None, None);
4113
4114        assert_eq!(FILEBUF, outstring);
4115    }
4116
4117    #[test]
4118    fn list_valid_sub_elements() {
4119        let model = AutosarModel::new();
4120        model.create_file("test.arxml", AutosarVersion::Autosar_4_3_0).unwrap();
4121        let el_autosar = model.root_element();
4122        let el_elements = el_autosar
4123            .create_sub_element(ElementName::ArPackages)
4124            .and_then(|el| el.create_named_sub_element(ElementName::ArPackage, "Package"))
4125            .and_then(|el| el.create_sub_element(ElementName::Elements))
4126            .unwrap();
4127        let result = el_elements.list_valid_sub_elements();
4128        assert!(!result.is_empty());
4129    }
4130
4131    #[test]
4132    fn check_version_compatibility() {
4133        const FILEBUF: &str = r#"<?xml version="1.0" encoding="utf-8"?>
4134<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">
4135  <AR-PACKAGES>
4136    <AR-PACKAGE>
4137      <SHORT-NAME>Pkg</SHORT-NAME>
4138      <ELEMENTS>
4139        <ACL-OBJECT-SET UUID="012345">
4140          <SHORT-NAME BLUEPRINT-VALUE="xyz">AclObjectSet</SHORT-NAME>
4141          <DERIVED-FROM-BLUEPRINT-REFS>
4142            <DERIVED-FROM-BLUEPRINT-REF DEST="ABSTRACT-IMPLEMENTATION-DATA-TYPE">/invalid</DERIVED-FROM-BLUEPRINT-REF>
4143          </DERIVED-FROM-BLUEPRINT-REFS>
4144        </ACL-OBJECT-SET>
4145        <ADAPTIVE-APPLICATION-SW-COMPONENT-TYPE>
4146          <SHORT-NAME>AdaptiveApplicationSwComponentType</SHORT-NAME>
4147        </ADAPTIVE-APPLICATION-SW-COMPONENT-TYPE>
4148      </ELEMENTS>
4149    </AR-PACKAGE>
4150  </AR-PACKAGES>
4151</AUTOSAR>"#;
4152        let model = AutosarModel::new();
4153        let (file, _) = model
4154            .load_buffer(FILEBUF.as_bytes(), OsString::from("test"), true)
4155            .unwrap();
4156        model.files().next().unwrap();
4157        let el_autosar = model.root_element();
4158
4159        let (compat_errors, _) =
4160            el_autosar.check_version_compatibility(&file.downgrade(), AutosarVersion::Autosar_4_3_0);
4161        assert_eq!(compat_errors.len(), 3);
4162
4163        for ce in compat_errors {
4164            match ce {
4165                CompatibilityError::IncompatibleElement { element, .. } => {
4166                    assert_eq!(element.element_name(), ElementName::AdaptiveApplicationSwComponentType);
4167                }
4168                CompatibilityError::IncompatibleAttribute { element, attribute, .. } => {
4169                    assert_eq!(element.element_name(), ElementName::ShortName);
4170                    assert_eq!(attribute, AttributeName::BlueprintValue);
4171                }
4172                CompatibilityError::IncompatibleAttributeValue { element, attribute, .. } => {
4173                    assert_eq!(element.element_name(), ElementName::DerivedFromBlueprintRef);
4174                    assert_eq!(attribute, AttributeName::Dest);
4175                }
4176            }
4177        }
4178
4179        // regression test - CompuScales in CompuInternalToPhys was falsely detected as incompatible
4180        let model = AutosarModel::new();
4181        let file = model.create_file("filename", AutosarVersion::Autosar_00046).unwrap();
4182        model
4183            .root_element()
4184            .create_sub_element(ElementName::ArPackages)
4185            .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
4186            .and_then(|e| e.create_sub_element(ElementName::Elements))
4187            .and_then(|e| e.create_named_sub_element(ElementName::CompuMethod, "CompuMethod"))
4188            .and_then(|e| e.create_sub_element(ElementName::CompuInternalToPhys))
4189            .and_then(|e| e.create_sub_element(ElementName::CompuScales))
4190            .and_then(|e| e.create_sub_element(ElementName::CompuScale))
4191            .unwrap();
4192        let (compat_errors, _) = file.check_version_compatibility(AutosarVersion::Autosar_4_3_0);
4193        assert!(compat_errors.is_empty());
4194    }
4195
4196    #[test]
4197    fn find_element_insert_pos() {
4198        let model = AutosarModel::new();
4199        model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
4200        let el_autosar = model.root_element();
4201        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4202        let el_ar_package = el_ar_packages
4203            .create_named_sub_element(ElementName::ArPackage, "Pkg")
4204            .unwrap();
4205        let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
4206
4207        // find_element_insert_pos does not operat on CharacterData elements, e.g. SHORT-NAME
4208        assert!(
4209            el_short_name
4210                .0
4211                .read()
4212                .calc_element_insert_range(ElementName::Desc, AutosarVersion::Autosar_00050)
4213                .is_err()
4214        );
4215
4216        // find_element_insert_pos fails to find a place for a sequence element with multiplicity 0-1
4217        assert!(
4218            el_autosar
4219                .0
4220                .read()
4221                .calc_element_insert_range(ElementName::ArPackages, AutosarVersion::Autosar_00050)
4222                .is_err()
4223        );
4224    }
4225
4226    #[test]
4227    fn sort() {
4228        let model = AutosarModel::new();
4229        model.create_file("test", AutosarVersion::Autosar_00050).unwrap();
4230        let el_autosar = model.root_element();
4231        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4232        let el_ar_package1 = el_ar_packages
4233            .create_named_sub_element(ElementName::ArPackage, "Z")
4234            .unwrap();
4235        let el_ar_package2 = el_ar_packages
4236            .create_named_sub_element(ElementName::ArPackage, "A")
4237            .unwrap();
4238        let el_elements = el_ar_package1.create_sub_element(ElementName::Elements).unwrap();
4239        el_ar_package1.create_sub_element(ElementName::AdminData).unwrap();
4240        // create some bsw values to sort inside el_ar_package1 "Z"
4241        let el_emcv = el_elements
4242            .create_named_sub_element(ElementName::EcucModuleConfigurationValues, "Config")
4243            .unwrap();
4244        let el_containers = el_emcv.create_sub_element(ElementName::Containers).unwrap();
4245        let el_ecv = el_containers
4246            .create_named_sub_element(ElementName::EcucContainerValue, "ConfigValues")
4247            .unwrap();
4248        let el_paramvalues = el_ecv.create_sub_element(ElementName::ParameterValues).unwrap();
4249        // first bsw value
4250        let el_value1 = el_paramvalues
4251            .create_sub_element(ElementName::EcucNumericalParamValue)
4252            .unwrap();
4253        let el_defref1 = el_value1.create_sub_element(ElementName::DefinitionRef).unwrap();
4254        el_defref1
4255            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::EcucBooleanParamDef))
4256            .unwrap();
4257        el_defref1.set_character_data("/DefRef_999").unwrap();
4258        // second bsw value
4259        let el_value2 = el_paramvalues
4260            .create_sub_element(ElementName::EcucNumericalParamValue)
4261            .unwrap();
4262        let el_defref2 = el_value2.create_sub_element(ElementName::DefinitionRef).unwrap();
4263        el_defref2
4264            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::EcucBooleanParamDef))
4265            .unwrap();
4266        el_defref2.set_character_data("/DefRef_111").unwrap();
4267        // Create some misc value sto sort inside el_ar_package2 "A"
4268        let el_elements2 = el_ar_package2.create_sub_element(ElementName::Elements).unwrap();
4269        let el_system = el_elements2
4270            .create_named_sub_element(ElementName::System, "System")
4271            .unwrap();
4272        let el_fibex_elements = el_system.create_sub_element(ElementName::FibexElements).unwrap();
4273        let el_fibex_element1 = el_fibex_elements
4274            .create_sub_element(ElementName::FibexElementRefConditional)
4275            .unwrap();
4276        let el_fibex_element_ref1 = el_fibex_element1
4277            .create_sub_element(ElementName::FibexElementRef)
4278            .unwrap();
4279        el_fibex_element_ref1
4280            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
4281            .unwrap();
4282        el_fibex_element_ref1.set_character_data("/ZZZZZ").unwrap();
4283        let el_fibex_element2 = el_fibex_elements
4284            .create_sub_element(ElementName::FibexElementRefConditional)
4285            .unwrap();
4286        let el_fibex_element_ref2 = el_fibex_element2
4287            .create_sub_element(ElementName::FibexElementRef)
4288            .unwrap();
4289        el_fibex_element_ref2
4290            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
4291            .unwrap();
4292        el_fibex_element_ref2.set_character_data("/AAAAA").unwrap();
4293
4294        model.sort();
4295        // validate that identifiable elements have been sorted
4296        let mut iter = el_ar_packages.sub_elements();
4297        let item1 = iter.next().unwrap();
4298        let item2 = iter.next().unwrap();
4299        assert_eq!(item1.item_name().unwrap(), "A");
4300        assert_eq!(item2.item_name().unwrap(), "Z");
4301
4302        // validate that BSW parameter values have been sorted
4303        let mut iter = el_paramvalues.sub_elements();
4304        let item1 = iter.next().unwrap();
4305        let item2 = iter.next().unwrap();
4306        assert_eq!(item1, el_value2);
4307        assert_eq!(item2, el_value1);
4308
4309        // validate that the misc elements (FIBEX-ELEMENT-REF-CONDITIONAL) have been sorted
4310        let mut iter = el_fibex_elements.sub_elements();
4311        let item1 = iter.next().unwrap();
4312        let item2 = iter.next().unwrap();
4313        assert_eq!(item1, el_fibex_element2);
4314        assert_eq!(item2, el_fibex_element1);
4315    }
4316
4317    fn helper_create_bsw_subelem(
4318        el_subcontainers: &Element,
4319        short_name: &str,
4320        defref: &str,
4321    ) -> Result<Element, AutosarDataError> {
4322        let e = el_subcontainers.create_named_sub_element(ElementName::EcucContainerValue, short_name)?;
4323        let defrefelem = e.create_sub_element(ElementName::DefinitionRef)?;
4324        defrefelem.set_character_data(defref)?;
4325        Ok(e)
4326    }
4327
4328    fn helper_create_indexed_bsw_subelem(
4329        el_subcontainers: &Element,
4330        short_name: &str,
4331        indexstr: &str,
4332        defref: &str,
4333    ) -> Result<Element, AutosarDataError> {
4334        let e = helper_create_bsw_subelem(el_subcontainers, short_name, defref)?;
4335        let indexelem = e.create_sub_element(ElementName::Index)?;
4336        indexelem.set_character_data(indexstr)?;
4337        Ok(e)
4338    }
4339
4340    #[test]
4341    fn sort_bsw_elements() {
4342        let model = AutosarModel::new();
4343        model.create_file("test", AutosarVersion::LATEST).unwrap();
4344        let el_subcontainers = model
4345            .root_element()
4346            .create_sub_element(ElementName::ArPackages)
4347            .and_then(|ap| ap.create_named_sub_element(ElementName::ArPackage, "Pkg"))
4348            .and_then(|ap| ap.create_sub_element(ElementName::Elements))
4349            .and_then(|elems| elems.create_named_sub_element(ElementName::EcucModuleConfigurationValues, "Config"))
4350            .and_then(|emcv| emcv.create_sub_element(ElementName::Containers))
4351            .and_then(|c| c.create_named_sub_element(ElementName::EcucContainerValue, "ConfigValues"))
4352            .and_then(|ecv| ecv.create_sub_element(ElementName::SubContainers))
4353            .unwrap();
4354        let elem1 =
4355            helper_create_indexed_bsw_subelem(&el_subcontainers, "Aaa", "06", "/Defref/Container/Value").unwrap(); // idx 6
4356        let elem2 =
4357            helper_create_indexed_bsw_subelem(&el_subcontainers, "Bbb", "5", "/Defref/Container/Value").unwrap(); // idx 5
4358        let elem3 =
4359            helper_create_indexed_bsw_subelem(&el_subcontainers, "Bbb2", "5", "/Defref/Container/Value").unwrap(); // idx 5 duplicate
4360        let elem4 =
4361            helper_create_indexed_bsw_subelem(&el_subcontainers, "Ccc", "0X4", "/Defref/Container/Value").unwrap(); // idx 4
4362        let elem5 = helper_create_bsw_subelem(&el_subcontainers, "Zzz", "/Defref/Container/Value").unwrap();
4363        let elem6 =
4364            helper_create_indexed_bsw_subelem(&el_subcontainers, "Ddd", "0b1", "/Defref/Container/Value").unwrap(); // idx 1
4365        let elem7 =
4366            helper_create_indexed_bsw_subelem(&el_subcontainers, "Eee", "0x3", "/Defref/Container/Value").unwrap(); // idx 3
4367        let elem8 =
4368            helper_create_indexed_bsw_subelem(&el_subcontainers, "Fff", "0B10", "/Defref/Container/Value").unwrap(); // idx 2
4369        let elem9 =
4370            helper_create_indexed_bsw_subelem(&el_subcontainers, "Ggg", "0", "/Defref/Container/Value").unwrap(); // idx 0
4371
4372        let elem10 = helper_create_bsw_subelem(&el_subcontainers, "Mmm_0", "/Defref/Container/Value").unwrap();
4373        let elem11 = helper_create_bsw_subelem(&el_subcontainers, "Mmm_5", "/Defref/Container/Value").unwrap();
4374        let elem12 = helper_create_bsw_subelem(&el_subcontainers, "Mmm_10", "/Defref/Container/Value").unwrap();
4375        let elem13 = helper_create_bsw_subelem(&el_subcontainers, "Mmm_9", "/Defref/Container/Value").unwrap();
4376
4377        el_subcontainers.sort();
4378        assert_eq!(elem1.position().unwrap(), 7);
4379        assert_eq!(elem2.position().unwrap(), 5);
4380        assert_eq!(elem3.position().unwrap(), 6);
4381        assert_eq!(elem4.position().unwrap(), 4);
4382        assert_eq!(elem6.position().unwrap(), 1);
4383        assert_eq!(elem7.position().unwrap(), 3);
4384        assert_eq!(elem8.position().unwrap(), 2);
4385        assert_eq!(elem9.position().unwrap(), 0);
4386        // elements without indices are sorted behind the indexed elements
4387        assert_eq!(elem10.position().unwrap(), 8);
4388        assert_eq!(elem11.position().unwrap(), 9);
4389        assert_eq!(elem13.position().unwrap(), 10);
4390        assert_eq!(elem12.position().unwrap(), 11);
4391        assert_eq!(elem5.position().unwrap(), 12);
4392    }
4393
4394    #[test]
4395    fn file_membership() {
4396        let model = AutosarModel::new();
4397        let file1 = model.create_file("test_1", AutosarVersion::Autosar_00050).unwrap();
4398        let file2 = model.create_file("test_2", AutosarVersion::Autosar_00050).unwrap();
4399        let el_autosar = model.root_element();
4400        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4401        let el_ar_package = el_ar_packages
4402            .create_named_sub_element(ElementName::ArPackage, "Pkg")
4403            .unwrap();
4404        el_ar_package.create_sub_element(ElementName::Elements).unwrap();
4405
4406        let fm: HashSet<WeakArxmlFile> = [file1.downgrade()].iter().cloned().collect();
4407        // setting the file membership of el_ar_packages should fail
4408        // its parent is not splittable, so this is not allowed
4409        el_ar_packages.set_file_membership(fm.clone());
4410        let (local, _) = el_ar_package.file_membership().unwrap();
4411        assert!(!local);
4412
4413        // setting the file membership of el_ar_package should succeed
4414        // this element is only part of file1, and is only serialized with file1
4415        el_ar_package.set_file_membership(fm.clone());
4416        let (local, fm2) = el_ar_package.file_membership().unwrap();
4417        assert!(local);
4418        assert_eq!(fm, fm2);
4419        let filetxt1 = file1.serialize().unwrap();
4420        let filetxt2 = file2.serialize().unwrap();
4421        assert_ne!(filetxt1, filetxt2);
4422
4423        // can't use a file from a different model in add_to_file / remove_from_file
4424        let model2 = AutosarModel::new();
4425        let model2_file = model2.create_file("file", AutosarVersion::LATEST).unwrap();
4426        assert!(el_ar_package.add_to_file(&model2_file).is_err());
4427        assert!(el_ar_package.remove_from_file(&model2_file).is_err());
4428
4429        // adding el_ar_package to file1 does nothing, since it is already present in this file
4430        el_ar_package.add_to_file(&file1).unwrap();
4431        let (local, fm3) = el_ar_package.file_membership().unwrap();
4432        assert!(local);
4433        assert_eq!(fm3.len(), 1);
4434
4435        // removing el_ar_package from file2 does nothing, it is not present in this file
4436        el_ar_package.remove_from_file(&file2).unwrap();
4437        let (local, fm3) = el_ar_package.file_membership().unwrap();
4438        assert!(local);
4439        assert_eq!(fm3.len(), 1);
4440
4441        // adding el_ar_package to file2 succeeds
4442        el_ar_package.add_to_file(&file2).unwrap();
4443        let (local, fm3) = el_ar_package.file_membership().unwrap();
4444        assert!(local);
4445        assert_eq!(fm3.len(), 2);
4446
4447        // removing el_ar_package from file1 and file2 causes it to be deleted
4448        assert!(el_ar_package.get_sub_element(ElementName::Elements).is_some());
4449        el_ar_package.remove_from_file(&file1).unwrap();
4450        el_ar_package.remove_from_file(&file2).unwrap();
4451        assert!(el_ar_package.get_sub_element(ElementName::Elements).is_none());
4452        assert!(el_ar_package.remove_from_file(&file2).is_err());
4453    }
4454
4455    #[test]
4456    fn remove_from_last_file() {
4457        let model = AutosarModel::new();
4458        let file = model.create_file("test.arxml", AutosarVersion::LATEST).unwrap();
4459        let el_autosar = model.root_element();
4460        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4461        let el_ar_package = el_ar_packages
4462            .create_named_sub_element(ElementName::ArPackage, "Pkg")
4463            .unwrap();
4464
4465        // removing the root element from the only file is forbidden, since the root element cannot be deleted
4466        let result = el_autosar.remove_from_file(&file);
4467        assert!(matches!(result, Err(AutosarDataError::RootElementRemovalForbidden)));
4468        // the model remains intact and usable
4469        assert_eq!(model.files().count(), 1);
4470        assert!(el_autosar.min_version().is_ok());
4471        assert!(file.serialize().is_ok());
4472
4473        // CHAPTER is both identifiable and splittable, so remove_from_file can be called for its
4474        // SHORT-NAME sub element, but deleting a SHORT-NAME is forbidden and the failure must be reported
4475        let el_chapter = el_ar_package
4476            .create_sub_element(ElementName::Elements)
4477            .and_then(|elements| elements.create_named_sub_element(ElementName::Documentation, "Doc"))
4478            .and_then(|doc| doc.create_sub_element(ElementName::DocumentationContent))
4479            .and_then(|content| content.create_named_sub_element(ElementName::Chapter, "Chap"))
4480            .unwrap();
4481        let el_short_name = el_chapter.get_sub_element(ElementName::ShortName).unwrap();
4482        let result = el_short_name.remove_from_file(&file);
4483        assert!(matches!(result, Err(AutosarDataError::ShortNameRemovalForbidden)));
4484        // the SHORT-NAME element is unchanged and still part of the file
4485        assert_eq!(el_chapter.item_name().unwrap(), "Chap");
4486        assert!(!el_short_name.file_membership().unwrap().1.is_empty());
4487
4488        // an ordinary element is deleted when it is removed from the only file that contains it
4489        el_ar_package.remove_from_file(&file).unwrap();
4490        assert!(model.get_element_by_path("/Pkg").is_none());
4491    }
4492
4493    #[test]
4494    fn comment() {
4495        let model = AutosarModel::new();
4496        model.create_file("test", AutosarVersion::LATEST).unwrap();
4497        let el_autosar = model.root_element();
4498
4499        // initially there is no comment
4500        assert!(el_autosar.comment().is_none());
4501
4502        // set and get a comment
4503        el_autosar.set_comment(Some("comment".to_string()));
4504        assert_eq!(el_autosar.comment().unwrap(), "comment");
4505
4506        // set a new comment containing "--" which is a forbidden sequence in XML comments
4507        el_autosar.set_comment(Some("comment--".to_string()));
4508        assert_eq!(el_autosar.comment().unwrap(), "comment__");
4509
4510        // remove the comment
4511        el_autosar.set_comment(None);
4512        assert!(el_autosar.comment().is_none());
4513    }
4514
4515    #[test]
4516    fn min_version() {
4517        let model = AutosarModel::new();
4518        let result = model.root_element().min_version();
4519        assert!(result.is_err());
4520
4521        model.create_file("test", AutosarVersion::LATEST).unwrap();
4522        let min_ver = model.root_element().min_version().unwrap();
4523        assert_eq!(min_ver, AutosarVersion::LATEST);
4524
4525        model.create_file("test2", AutosarVersion::Autosar_00042).unwrap();
4526        let min_ver = model.root_element().min_version().unwrap();
4527        assert_eq!(min_ver, AutosarVersion::Autosar_00042);
4528    }
4529
4530    #[test]
4531    fn traits() {
4532        let model = AutosarModel::new();
4533        model.create_file("test", AutosarVersion::LATEST).unwrap();
4534
4535        // traits of elements
4536        let el_autosar = model.root_element();
4537        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4538
4539        let el_autosar_second_ref = el_autosar.clone();
4540        assert_eq!(el_autosar, el_autosar_second_ref);
4541        assert_eq!(format!("{el_autosar:?}"), format!("{el_autosar_second_ref:?}"));
4542        assert_ne!(el_autosar, el_ar_packages);
4543
4544        let weak1 = el_autosar.downgrade();
4545        let weak2 = el_autosar_second_ref.downgrade();
4546        assert_eq!(weak1, weak2);
4547        assert_eq!(format!("{weak1:?}"), format!("{weak2:?}"));
4548
4549        #[allow(clippy::mutable_key_type)]
4550        let mut hs = HashSet::new();
4551        hs.insert(el_autosar);
4552        hs.insert(el_ar_packages);
4553        // can't insert el_autosar_second_ref, it is already in the set
4554        assert!(!hs.insert(el_autosar_second_ref));
4555        assert_eq!(hs.len(), 2);
4556
4557        let mut hs2 = HashSet::new();
4558        hs2.insert(weak1);
4559        assert!(!hs2.insert(weak2));
4560        assert_eq!(hs2.len(), 1);
4561
4562        // traits of elementcontent
4563        let ec_elem = ElementContent::Element(model.root_element());
4564        assert_eq!(format!("{:?}", model.root_element()), format!("{ec_elem:?}"));
4565        assert_eq!(ec_elem.unwrap_element(), Some(model.root_element()));
4566        assert!(ec_elem.unwrap_cdata().is_none());
4567        let cdata = CharacterData::String("test".to_string());
4568        let ec_chars = ElementContent::CharacterData(cdata.clone());
4569        assert_eq!(format!("{cdata:?}"), format!("{ec_chars:?}"));
4570        assert_eq!(ec_chars.unwrap_cdata(), Some(cdata));
4571        assert!(ec_chars.unwrap_element().is_none());
4572    }
4573
4574    #[test]
4575    fn element_order() {
4576        let model = AutosarModel::new();
4577        let _file = model.create_file("test", AutosarVersion::LATEST).unwrap();
4578        let el_autosar = model.root_element();
4579        let el_elements = el_autosar
4580            .create_sub_element(ElementName::ArPackages)
4581            .unwrap()
4582            .create_named_sub_element(ElementName::ArPackage, "pkg")
4583            .unwrap()
4584            .create_sub_element(ElementName::Elements)
4585            .unwrap();
4586        let el_system = el_elements
4587            .create_named_sub_element(ElementName::System, "sys")
4588            .unwrap();
4589        let fibex_elements = el_system.create_sub_element(ElementName::FibexElements).unwrap();
4590
4591        let item1 = el_elements
4592            .create_named_sub_element(ElementName::ApplicationPrimitiveDataType, "adt_2")
4593            .unwrap();
4594        let item2 = el_elements
4595            .create_named_sub_element(ElementName::ApplicationPrimitiveDataType, "adt_10")
4596            .unwrap();
4597        let item3 = el_elements
4598            .create_named_sub_element(ElementName::ApplicationArrayDataType, "adt_12")
4599            .unwrap();
4600        // items 1 and 2 are sorted after separating the index from the name, so 10 comes after 2
4601        assert!(item1 < item2);
4602        // items 2 and 3 are sorted by the element type, so in this case the index does not matter
4603        assert!(item3 < item1);
4604
4605        let item4 = fibex_elements
4606            .create_sub_element(ElementName::FibexElementRefConditional)
4607            .unwrap();
4608        let item4_ref = item4.create_sub_element(ElementName::FibexElementRef).unwrap();
4609        item4_ref
4610            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
4611            .unwrap();
4612        item4_ref.set_character_data("/aaa").unwrap();
4613
4614        let item5 = fibex_elements
4615            .create_sub_element(ElementName::FibexElementRefConditional)
4616            .unwrap();
4617        let item5_ref = item5.create_sub_element(ElementName::FibexElementRef).unwrap();
4618        item5_ref
4619            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::ISignal))
4620            .unwrap();
4621        item5_ref.set_character_data("/bbb").unwrap();
4622
4623        // items 4 and 5 are sorted by the character data of the reference
4624        assert!(item4 < item5);
4625
4626        let item6 = fibex_elements
4627            .create_sub_element(ElementName::FibexElementRefConditional)
4628            .unwrap();
4629        let item6_ref = item6.create_sub_element(ElementName::FibexElementRef).unwrap();
4630        item6_ref
4631            .set_attribute(AttributeName::Dest, CharacterData::Enum(EnumItem::EcuInstance))
4632            .unwrap();
4633
4634        // items 4 and 6 are sorted by the DEST attribute of the reference
4635        assert!(item6 < item4);
4636
4637        let item7 = fibex_elements
4638            .create_sub_element(ElementName::FibexElementRefConditional)
4639            .unwrap();
4640        item7.create_sub_element(ElementName::FibexElementRef).unwrap();
4641
4642        // item7 is incomplete, lacking the DEST attribute so it is sorted last
4643        assert!(item7 > item6);
4644        assert!(item6 < item7);
4645    }
4646
4647    #[test]
4648    fn elements_dfs_with_max_depth() {
4649        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
4650        <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">
4651        <AR-PACKAGES>
4652          <AR-PACKAGE><SHORT-NAME>Pkg_A</SHORT-NAME><ELEMENTS>
4653            <ECUC-MODULE-CONFIGURATION-VALUES><SHORT-NAME>BswModule</SHORT-NAME><CONTAINERS><ECUC-CONTAINER-VALUE>
4654              <SHORT-NAME>BswModuleValues</SHORT-NAME>
4655              <PARAMETER-VALUES>
4656                <ECUC-NUMERICAL-PARAM-VALUE>
4657                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_A</DEFINITION-REF>
4658                </ECUC-NUMERICAL-PARAM-VALUE>
4659                <ECUC-NUMERICAL-PARAM-VALUE>
4660                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_B</DEFINITION-REF>
4661                </ECUC-NUMERICAL-PARAM-VALUE>
4662                <ECUC-NUMERICAL-PARAM-VALUE>
4663                  <DEFINITION-REF DEST="ECUC-BOOLEAN-PARAM-DEF">/REF_C</DEFINITION-REF>
4664                </ECUC-NUMERICAL-PARAM-VALUE>
4665              </PARAMETER-VALUES>
4666            </ECUC-CONTAINER-VALUE></CONTAINERS></ECUC-MODULE-CONFIGURATION-VALUES>
4667          </ELEMENTS></AR-PACKAGE>
4668          <AR-PACKAGE><SHORT-NAME>Pkg_B</SHORT-NAME></AR-PACKAGE>
4669          <AR-PACKAGE><SHORT-NAME>Pkg_C</SHORT-NAME></AR-PACKAGE>
4670        </AR-PACKAGES></AUTOSAR>"#.as_bytes();
4671        let model = AutosarModel::new();
4672        let (_, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
4673        let root_elem = model.root_element();
4674        let ar_packages_elem = root_elem.get_sub_element(ElementName::ArPackages).unwrap();
4675        let root_all_count = root_elem.elements_dfs().count();
4676        let ar_packages_all_count = ar_packages_elem.elements_dfs().count();
4677        assert_eq!(root_all_count, ar_packages_all_count + 1);
4678
4679        let root_lvl3_count = root_elem.elements_dfs_with_max_depth(3).count();
4680        let ar_packages_lvl2_count = ar_packages_elem.elements_dfs_with_max_depth(2).count();
4681        assert_eq!(root_lvl3_count, ar_packages_lvl2_count + 1);
4682
4683        root_elem
4684            .elements_dfs_with_max_depth(3)
4685            .skip(1)
4686            .zip(ar_packages_elem.elements_dfs_with_max_depth(2))
4687            .for_each(|((_, x), (_, y))| assert_eq!(x, y));
4688
4689        for elem in ar_packages_elem.elements_dfs_with_max_depth(2) {
4690            assert!(elem.0 <= 2);
4691        }
4692    }
4693
4694    #[test]
4695    fn parse_reference_bases() {
4696        // from issue #36
4697        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
4698<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_00048.xsd">
4699  <AR-PACKAGES>
4700    <AR-PACKAGE>
4701      <SHORT-NAME>My</SHORT-NAME>
4702      <AR-PACKAGES>
4703        <AR-PACKAGE>
4704          <SHORT-NAME>Lib</SHORT-NAME>
4705          <AR-PACKAGES>
4706            <AR-PACKAGE>
4707              <SHORT-NAME>Hierarchy</SHORT-NAME>
4708              <AR-PACKAGES>
4709                <AR-PACKAGE>
4710                  <SHORT-NAME>Units</SHORT-NAME>
4711                  <ELEMENTS>
4712                    <UNIT>
4713                      <SHORT-NAME>MyUnit</SHORT-NAME>
4714                      <FACTOR-SI-TO-UNIT>1</FACTOR-SI-TO-UNIT>
4715                      <OFFSET-SI-TO-UNIT>0</OFFSET-SI-TO-UNIT>
4716                    </UNIT>
4717                  </ELEMENTS>
4718                </AR-PACKAGE>
4719              </AR-PACKAGES>
4720            </AR-PACKAGE>
4721          </AR-PACKAGES>
4722        </AR-PACKAGE>
4723        <AR-PACKAGE>
4724          <SHORT-NAME>SWC</SHORT-NAME>
4725          <REFERENCE-BASES>
4726            <REFERENCE-BASE>
4727              <SHORT-LABEL>Units</SHORT-LABEL>
4728              <PACKAGE-REF DEST="AR-PACKAGE">/My/Lib/Hierarchy/Units</PACKAGE-REF>
4729            </REFERENCE-BASE>
4730          </REFERENCE-BASES>
4731          <AR-PACKAGES>
4732            <AR-PACKAGE>
4733              <SHORT-NAME>ApplicationDataTypes</SHORT-NAME>
4734              <ELEMENTS>
4735                <APPLICATION-PRIMITIVE-DATA-TYPE>
4736                  <SHORT-NAME>MyDataType</SHORT-NAME>
4737                  <CATEGORY>VALUE</CATEGORY>
4738                  <SW-DATA-DEF-PROPS>
4739                    <SW-DATA-DEF-PROPS-VARIANTS>
4740                      <SW-DATA-DEF-PROPS-CONDITIONAL>
4741                        <SW-CALIBRATION-ACCESS>NOT-ACCESSIBLE</SW-CALIBRATION-ACCESS>
4742                        <UNIT-REF DEST="UNIT" BASE="Units">MyUnit</UNIT-REF>
4743                      </SW-DATA-DEF-PROPS-CONDITIONAL>
4744                    </SW-DATA-DEF-PROPS-VARIANTS>
4745                  </SW-DATA-DEF-PROPS>
4746                </APPLICATION-PRIMITIVE-DATA-TYPE>
4747              </ELEMENTS>
4748            </AR-PACKAGE>
4749          </AR-PACKAGES>
4750        </AR-PACKAGE>
4751      </AR-PACKAGES>
4752    </AR-PACKAGE>
4753  </AR-PACKAGES>
4754</AUTOSAR>"#.as_bytes();
4755        let model = AutosarModel::new();
4756        let (_, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
4757
4758        let unit_elem = model.get_element_by_path("/My/Lib/Hierarchy/Units/MyUnit").unwrap();
4759        assert_eq!(unit_elem.element_name(), ElementName::Unit);
4760
4761        // verify that we're correctly tracking incoming references to the unit element from the reference base
4762        let refs_to_unit = model.get_references_to(&unit_elem.path().unwrap());
4763        assert_eq!(refs_to_unit.len(), 1);
4764        let reference_elem = refs_to_unit[0].upgrade().unwrap();
4765        assert_eq!(reference_elem.element_name(), ElementName::UnitRef);
4766        assert!(reference_elem.attribute_value(AttributeName::Base).is_some());
4767
4768        // verify that the reference base has been parsed correctly
4769        assert_eq!(
4770            reference_elem.resolve_reference_base("Units").as_deref(),
4771            Some("/My/Lib/Hierarchy/Units")
4772        );
4773
4774        // verify that we can navigate from the reference element to the target unit element
4775        let target = reference_elem.get_reference_target().unwrap();
4776        assert_eq!(target, unit_elem);
4777        assert_eq!(model.verify_reference_caches(), Ok(()));
4778    }
4779
4780    #[test]
4781    fn modify_relative_ref() {
4782        const FILEBUF: &[u8] = r#"<?xml version="1.0" encoding="utf-8"?>
4783<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_00048.xsd">
4784  <AR-PACKAGES>
4785    <AR-PACKAGE>
4786      <SHORT-NAME>First</SHORT-NAME>
4787      <AR-PACKAGES>
4788        <AR-PACKAGE>
4789          <SHORT-NAME>Second</SHORT-NAME>
4790          <ELEMENTS>
4791            <ECU-INSTANCE>
4792              <SHORT-NAME>Ecu</SHORT-NAME>
4793            </ECU-INSTANCE>
4794          </ELEMENTS>
4795        </AR-PACKAGE>
4796      </AR-PACKAGES>
4797    </AR-PACKAGE>
4798    <AR-PACKAGE>
4799      <SHORT-NAME>Ref</SHORT-NAME>
4800      <REFERENCE-BASES>
4801        <REFERENCE-BASE>
4802          <SHORT-LABEL>First</SHORT-LABEL>
4803          <PACKAGE-REF DEST="AR-PACKAGE">/First</PACKAGE-REF>
4804        </REFERENCE-BASE>
4805        <REFERENCE-BASE>
4806          <SHORT-LABEL>Second</SHORT-LABEL>
4807          <PACKAGE-REF DEST="AR-PACKAGE">/First/Second</PACKAGE-REF>
4808        </REFERENCE-BASE>
4809      </REFERENCE-BASES>
4810      <ELEMENTS>
4811        <SYSTEM>
4812          <SHORT-NAME>System</SHORT-NAME>
4813          <FIBEX-ELEMENTS>
4814            <FIBEX-ELEMENT-REF-CONDITIONAL>
4815              <FIBEX-ELEMENT-REF DEST="ECU-INSTANCE" BASE="First">Second/Ecu</FIBEX-ELEMENT-REF>
4816            </FIBEX-ELEMENT-REF-CONDITIONAL>
4817          </FIBEX-ELEMENTS>
4818        </SYSTEM>
4819      </ELEMENTS>
4820    </AR-PACKAGE>
4821  </AR-PACKAGES>
4822</AUTOSAR>"#.as_bytes();
4823        let model = AutosarModel::new();
4824        let (_, _) = model.load_buffer(FILEBUF, "test1", true).unwrap();
4825
4826        let ref_elem = model
4827            .get_element_by_path("/Ref/System")
4828            .unwrap()
4829            .get_sub_element(ElementName::FibexElements)
4830            .unwrap()
4831            .get_sub_element(ElementName::FibexElementRefConditional)
4832            .unwrap()
4833            .get_sub_element(ElementName::FibexElementRef)
4834            .unwrap();
4835        let ecu_elem = ref_elem.get_reference_target().unwrap();
4836        assert_eq!(ecu_elem.element_name(), ElementName::EcuInstance);
4837        assert_eq!(model.get_references_to(&ecu_elem.path().unwrap()).len(), 1);
4838
4839        ref_elem.set_relative_reference_target(&ecu_elem, "Second").unwrap();
4840        assert_eq!(ref_elem.character_data().unwrap().string_value().unwrap(), "Ecu");
4841        assert_eq!(model.get_references_to(&ecu_elem.path().unwrap()).len(), 1);
4842        assert_eq!(model.verify_reference_caches(), Ok(()));
4843    }
4844
4845    #[test]
4846    fn create_modify_delete_ref_bases() {
4847        let model = AutosarModel::new();
4848        model.create_file("test", AutosarVersion::LATEST).unwrap();
4849        let el_autosar = model.root_element();
4850        let el_ar_packages = el_autosar.create_sub_element(ElementName::ArPackages).unwrap();
4851        let el_ar_package = el_ar_packages
4852            .create_named_sub_element(ElementName::ArPackage, "Pkg")
4853            .unwrap();
4854        let el_ar_package2 = el_ar_packages
4855            .create_named_sub_element(ElementName::ArPackage, "Pkg2")
4856            .unwrap();
4857        let el_reference_bases = el_ar_package.create_sub_element(ElementName::ReferenceBases).unwrap();
4858        let el_reference_base = el_reference_bases
4859            .create_sub_element(ElementName::ReferenceBase)
4860            .unwrap();
4861        // the reference base is unusable, because label and packageref are missing
4862        assert_eq!(el_reference_base.resolve_reference_base("Units"), None);
4863        let el_short_label = el_reference_base.create_sub_element(ElementName::ShortLabel).unwrap();
4864        el_short_label.set_character_data("Units").unwrap();
4865        // still unusable, because packageref is missing
4866        assert_eq!(el_reference_base.resolve_reference_base("Units"), None);
4867        let el_package_ref = el_reference_base.create_sub_element(ElementName::PackageRef).unwrap();
4868        el_package_ref.set_reference_target(&el_ar_package2).unwrap();
4869        // the declaration is complete now, so the base can be resolved
4870        assert_eq!(
4871            el_reference_base.resolve_reference_base("Units").as_deref(),
4872            Some("/Pkg2")
4873        );
4874
4875        el_short_label.set_character_data("Modified").unwrap();
4876        // the base is now declared under the new label
4877        assert_eq!(el_reference_base.resolve_reference_base("Units"), None);
4878        assert_eq!(
4879            el_reference_base.resolve_reference_base("Modified").as_deref(),
4880            Some("/Pkg2")
4881        );
4882
4883        el_package_ref.set_reference_target(&el_ar_package).unwrap();
4884        // the base points at the new package
4885        assert_eq!(
4886            el_reference_base.resolve_reference_base("Modified").as_deref(),
4887            Some("/Pkg")
4888        );
4889
4890        el_reference_base.remove_sub_element(el_package_ref).unwrap();
4891        // unusable again, because the packageref is gone
4892        assert_eq!(el_reference_base.resolve_reference_base("Modified"), None);
4893        // create it again
4894        let el_package_ref = el_reference_base.create_sub_element(ElementName::PackageRef).unwrap();
4895        el_package_ref.set_reference_target(&el_ar_package2).unwrap();
4896        assert_eq!(
4897            el_reference_base.resolve_reference_base("Modified").as_deref(),
4898            Some("/Pkg2")
4899        );
4900        // remove the SHORT-LABEL
4901        el_reference_base.remove_sub_element(el_short_label).unwrap();
4902        // unusable again, because the label is missing
4903        assert_eq!(el_reference_base.resolve_reference_base("Modified"), None);
4904        let el_short_label = el_reference_base.create_sub_element(ElementName::ShortLabel).unwrap();
4905        el_short_label.set_character_data("Units").unwrap();
4906        // usable again, under the label it was just given
4907        assert_eq!(
4908            el_reference_base.resolve_reference_base("Units").as_deref(),
4909            Some("/Pkg2")
4910        );
4911
4912        el_reference_bases
4913            .remove_sub_element(el_reference_base.clone())
4914            .unwrap();
4915        // the declaration is gone with the element
4916        assert_eq!(el_reference_base.resolve_reference_base("Units"), None);
4917        assert_eq!(model.verify_reference_caches(), Ok(()));
4918    }
4919
4920    #[test]
4921    fn rename_reference_base() {
4922        let model = AutosarModel::new();
4923        model.create_file("test", AutosarVersion::LATEST).unwrap();
4924
4925        let el_ar_packages = model
4926            .root_element()
4927            .create_sub_element(ElementName::ArPackages)
4928            .unwrap();
4929        let owner1 = el_ar_packages
4930            .create_named_sub_element(ElementName::ArPackage, "Owner1")
4931            .unwrap();
4932        let owner2 = el_ar_packages
4933            .create_named_sub_element(ElementName::ArPackage, "Owner2")
4934            .unwrap();
4935        let target1 = el_ar_packages
4936            .create_named_sub_element(ElementName::ArPackage, "Target1")
4937            .unwrap();
4938        let target2 = el_ar_packages
4939            .create_named_sub_element(ElementName::ArPackage, "Target2")
4940            .unwrap();
4941
4942        let short_label_1 = owner1
4943            .create_sub_element(ElementName::ReferenceBases)
4944            .and_then(|e| e.create_sub_element(ElementName::ReferenceBase))
4945            .and_then(|e| {
4946                e.create_sub_element(ElementName::ShortLabel)
4947                    .and_then(|l| l.set_character_data("Shared").map(|_| l))
4948            })
4949            .unwrap();
4950        let owner1_reference_base = owner1
4951            .get_sub_element(ElementName::ReferenceBases)
4952            .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
4953            .unwrap();
4954        owner1_reference_base
4955            .create_sub_element(ElementName::PackageRef)
4956            .and_then(|p| p.set_reference_target(&target1))
4957            .unwrap();
4958
4959        owner2
4960            .create_sub_element(ElementName::ReferenceBases)
4961            .and_then(|e| e.create_sub_element(ElementName::ReferenceBase))
4962            .and_then(|e| {
4963                e.create_sub_element(ElementName::ShortLabel)
4964                    .and_then(|l| l.set_character_data("Shared").map(|_| l))
4965            })
4966            .unwrap();
4967        let owner2_reference_base = owner2
4968            .get_sub_element(ElementName::ReferenceBases)
4969            .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
4970            .unwrap();
4971        owner2_reference_base
4972            .create_sub_element(ElementName::PackageRef)
4973            .and_then(|p| p.set_reference_target(&target2))
4974            .unwrap();
4975
4976        // both packages declare "Shared", and each one sees only its own declaration
4977        assert_eq!(
4978            short_label_1.resolve_reference_base("Shared").as_deref(),
4979            Some("/Target1")
4980        );
4981        assert_eq!(
4982            owner2_reference_base.resolve_reference_base("Shared").as_deref(),
4983            Some("/Target2")
4984        );
4985
4986        short_label_1.set_character_data("Renamed").unwrap();
4987
4988        // only the declaration in /Owner1 was renamed
4989        assert_eq!(short_label_1.resolve_reference_base("Shared"), None);
4990        assert_eq!(
4991            short_label_1.resolve_reference_base("Renamed").as_deref(),
4992            Some("/Target1")
4993        );
4994        assert_eq!(
4995            owner2_reference_base.resolve_reference_base("Shared").as_deref(),
4996            Some("/Target2")
4997        );
4998        assert_eq!(owner2_reference_base.resolve_reference_base("Renamed"), None);
4999        assert_eq!(model.verify_reference_caches(), Ok(()));
5000    }
5001
5002    #[test]
5003    fn changing_base_attribute() {
5004        let model = AutosarModel::new();
5005        model.create_file("test", AutosarVersion::LATEST).unwrap();
5006
5007        let el_ar_packages = model
5008            .root_element()
5009            .create_sub_element(ElementName::ArPackages)
5010            .unwrap();
5011        let base_a = el_ar_packages
5012            .create_named_sub_element(ElementName::ArPackage, "BaseA")
5013            .unwrap();
5014        let base_b = el_ar_packages
5015            .create_named_sub_element(ElementName::ArPackage, "BaseB")
5016            .unwrap();
5017        let ref_pkg = el_ar_packages
5018            .create_named_sub_element(ElementName::ArPackage, "RefPkg")
5019            .unwrap();
5020
5021        let ecu_a = base_a
5022            .create_sub_element(ElementName::Elements)
5023            .and_then(|e| e.create_named_sub_element(ElementName::EcuInstance, "Ecu"))
5024            .unwrap();
5025        let _ecu_b = base_b
5026            .create_sub_element(ElementName::Elements)
5027            .and_then(|e| e.create_named_sub_element(ElementName::EcuInstance, "Ecu"))
5028            .unwrap();
5029
5030        let ref_bases = ref_pkg.create_sub_element(ElementName::ReferenceBases).unwrap();
5031        let rb_a = ref_bases.create_sub_element(ElementName::ReferenceBase).unwrap();
5032        rb_a.create_sub_element(ElementName::ShortLabel)
5033            .and_then(|e| e.set_character_data("A"))
5034            .unwrap();
5035        rb_a.create_sub_element(ElementName::PackageRef)
5036            .and_then(|e| e.set_reference_target(&base_a))
5037            .unwrap();
5038
5039        let rb_b = ref_bases.create_sub_element(ElementName::ReferenceBase).unwrap();
5040        rb_b.create_sub_element(ElementName::ShortLabel)
5041            .and_then(|e| e.set_character_data("B"))
5042            .unwrap();
5043        rb_b.create_sub_element(ElementName::PackageRef)
5044            .and_then(|e| e.set_reference_target(&base_b))
5045            .unwrap();
5046
5047        let ref_elem = ref_pkg
5048            .create_sub_element(ElementName::Elements)
5049            .and_then(|e| e.create_named_sub_element(ElementName::System, "Sys"))
5050            .and_then(|e| e.create_sub_element(ElementName::FibexElements))
5051            .and_then(|e| e.create_sub_element(ElementName::FibexElementRefConditional))
5052            .and_then(|e| e.create_sub_element(ElementName::FibexElementRef))
5053            .unwrap();
5054        ref_elem.set_relative_reference_target(&ecu_a, "A").unwrap();
5055
5056        ref_elem.set_attribute_string(AttributeName::Base, "B").unwrap();
5057
5058        // switching the BASE attribute re-resolves the reference: "Ecu" relative to base "B" is
5059        // /BaseB/Ecu, so that is the target path it is now registered under
5060        assert_eq!(
5061            model.relative_reference_target(&ref_elem.downgrade()).as_deref(),
5062            Some("/BaseB/Ecu")
5063        );
5064        assert_eq!(model.get_references_to("/BaseB/Ecu").len(), 1);
5065        assert!(model.get_references_to("/BaseA/Ecu").is_empty());
5066        assert_eq!(model.verify_reference_caches(), Ok(()));
5067    }
5068
5069    // helper for the reference base removal tests: create a REFERENCE-BASE with the given label
5070    // inside owner_package, pointing at target_package
5071    fn create_reference_base(owner_package: &Element, label: &str, target_package: &Element) {
5072        let el_reference_base = owner_package
5073            .get_or_create_sub_element(ElementName::ReferenceBases)
5074            .and_then(|e| e.create_sub_element(ElementName::ReferenceBase))
5075            .unwrap();
5076        el_reference_base
5077            .create_sub_element(ElementName::ShortLabel)
5078            .and_then(|e| e.set_character_data(label))
5079            .unwrap();
5080        el_reference_base
5081            .create_sub_element(ElementName::PackageRef)
5082            .and_then(|e| e.set_reference_target(target_package))
5083            .unwrap();
5084    }
5085
5086    // helper for the reference base removal tests: build a model containing the package
5087    // "/Outer/Owner", which declares two reference bases pointing at "/Target"
5088    fn build_reference_base_model() -> (AutosarModel, Element, Element) {
5089        let model = AutosarModel::new();
5090        model.create_file("test", AutosarVersion::LATEST).unwrap();
5091        let el_ar_packages = model
5092            .root_element()
5093            .create_sub_element(ElementName::ArPackages)
5094            .unwrap();
5095        let el_target = el_ar_packages
5096            .create_named_sub_element(ElementName::ArPackage, "Target")
5097            .unwrap();
5098        // the owner package is nested inside another package, so that the tests also verify that the
5099        // full path of the owner package is used, and not just the name of the innermost package
5100        let el_owner = el_ar_packages
5101            .create_named_sub_element(ElementName::ArPackage, "Outer")
5102            .and_then(|e| e.create_sub_element(ElementName::ArPackages))
5103            .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Owner"))
5104            .unwrap();
5105        create_reference_base(&el_owner, "BaseA", &el_target);
5106        create_reference_base(&el_owner, "BaseB", &el_target);
5107        // an element inside the owner package, from which the reference bases it declares are in scope
5108        let el_inside_owner = el_owner
5109            .create_sub_element(ElementName::Elements)
5110            .and_then(|e| e.create_named_sub_element(ElementName::System, "System"))
5111            .unwrap();
5112
5113        assert_eq!(
5114            el_inside_owner.resolve_reference_base("BaseA").as_deref(),
5115            Some("/Target")
5116        );
5117        assert_eq!(
5118            el_inside_owner.resolve_reference_base("BaseB").as_deref(),
5119            Some("/Target")
5120        );
5121
5122        (model, el_owner, el_inside_owner)
5123    }
5124
5125    #[test]
5126    fn remove_reference_bases_element() {
5127        // removing the REFERENCE-BASES element removes every reference base declared inside it
5128        let (model, el_owner, el_inside_owner) = build_reference_base_model();
5129        let el_reference_bases = el_owner.get_sub_element(ElementName::ReferenceBases).unwrap();
5130        el_owner.remove_sub_element(el_reference_bases).unwrap();
5131        assert_eq!(el_inside_owner.resolve_reference_base("BaseA"), None);
5132        assert_eq!(el_inside_owner.resolve_reference_base("BaseB"), None);
5133        assert_eq!(model.verify_reference_caches(), Ok(()));
5134    }
5135
5136    #[test]
5137    fn remove_reference_base_owner_package() {
5138        // Removing the AR-PACKAGE which owns the reference bases takes the declarations with it. A
5139        // reference base is only in scope inside its declaring package, so everything which could have
5140        // used it is removed at the same time.
5141        let (model, el_owner, el_inside_owner) = build_reference_base_model();
5142        let el_ar_packages = el_owner.parent().unwrap().unwrap();
5143        el_ar_packages.remove_sub_element(el_owner).unwrap();
5144        assert_eq!(el_inside_owner.resolve_reference_base("BaseA"), None);
5145        assert_eq!(model.verify_reference_caches(), Ok(()));
5146    }
5147
5148    #[test]
5149    fn remove_reference_base_ancestor() {
5150        // removing any element above the reference bases takes the declarations with it, no matter how
5151        // far up the hierarchy it is
5152        let (model, _el_owner, el_inside_owner) = build_reference_base_model();
5153        let el_ar_packages = model.root_element().get_sub_element(ElementName::ArPackages).unwrap();
5154        model.root_element().remove_sub_element(el_ar_packages).unwrap();
5155        assert_eq!(el_inside_owner.resolve_reference_base("BaseA"), None);
5156        assert_eq!(model.verify_reference_caches(), Ok(()));
5157    }
5158
5159    #[test]
5160    fn remove_reference_base_short_label_character_data() {
5161        // removing the character data of the SHORT-LABEL leaves the REFERENCE-BASE without a label,
5162        // so it can no longer be used and must be removed from the cache
5163        let (model, el_owner, el_inside_owner) = build_reference_base_model();
5164        let el_short_label = el_owner
5165            .get_sub_element(ElementName::ReferenceBases)
5166            .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
5167            .and_then(|e| e.get_sub_element(ElementName::ShortLabel))
5168            .unwrap();
5169        assert_eq!(
5170            el_short_label.character_data().unwrap().string_value().unwrap(),
5171            "BaseA"
5172        );
5173        el_short_label.remove_character_data().unwrap();
5174
5175        assert_eq!(el_inside_owner.resolve_reference_base("BaseA"), None);
5176        assert_eq!(
5177            el_inside_owner.resolve_reference_base("BaseB").as_deref(),
5178            Some("/Target")
5179        );
5180        assert_eq!(model.verify_reference_caches(), Ok(()));
5181    }
5182
5183    #[test]
5184    fn remove_reference_base_package_ref_character_data() {
5185        // removing the character data of the PACKAGE-REF leaves the REFERENCE-BASE without a target,
5186        // so it can no longer be used
5187        let (model, el_owner, el_inside_owner) = build_reference_base_model();
5188        let el_package_ref = el_owner
5189            .get_sub_element(ElementName::ReferenceBases)
5190            .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
5191            .and_then(|e| e.get_sub_element(ElementName::PackageRef))
5192            .unwrap();
5193        assert_eq!(
5194            el_package_ref.character_data().unwrap().string_value().unwrap(),
5195            "/Target"
5196        );
5197        el_package_ref.remove_character_data().unwrap();
5198
5199        assert_eq!(el_inside_owner.resolve_reference_base("BaseA"), None);
5200        assert_eq!(
5201            el_inside_owner.resolve_reference_base("BaseB").as_deref(),
5202            Some("/Target")
5203        );
5204        assert_eq!(model.verify_reference_caches(), Ok(()));
5205    }
5206
5207    // helper for the rename/move tests: get the PACKAGE-REF of the first REFERENCE-BASE of a package
5208    fn get_package_ref(owner_package: &Element) -> Element {
5209        owner_package
5210            .get_sub_element(ElementName::ReferenceBases)
5211            .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
5212            .and_then(|e| e.get_sub_element(ElementName::PackageRef))
5213            .unwrap()
5214    }
5215
5216    // helper for the rename/move tests: the path which the reference base used by `reference` resolves
5217    // to, i.e. the path that the relative content of the reference is interpreted against
5218    fn resolved_reference_base(reference: &Element) -> Option<String> {
5219        let base_label = reference
5220            .attribute_value(AttributeName::Base)
5221            .and_then(|cdata| cdata.string_value())?;
5222        reference.resolve_reference_base(&base_label)
5223    }
5224
5225    // helper for the rename/move tests: create a package containing an ECU-INSTANCE named "Ecu"
5226    fn create_target_package(ar_packages: &Element, name: &str) -> (Element, Element) {
5227        let el_package = ar_packages
5228            .create_named_sub_element(ElementName::ArPackage, name)
5229            .unwrap();
5230        let el_ecu = el_package
5231            .create_sub_element(ElementName::Elements)
5232            .and_then(|e| e.create_named_sub_element(ElementName::EcuInstance, "Ecu"))
5233            .unwrap();
5234        (el_package, el_ecu)
5235    }
5236
5237    // helper for the rename/move tests: create a FIBEX-ELEMENT-REF inside a new package
5238    fn create_relative_reference(
5239        ar_packages: &Element,
5240        package_name: &str,
5241        target: &Element,
5242        base_label: &str,
5243    ) -> Element {
5244        let el_ref = ar_packages
5245            .create_named_sub_element(ElementName::ArPackage, package_name)
5246            .and_then(|e| e.create_sub_element(ElementName::Elements))
5247            .and_then(|e| e.create_named_sub_element(ElementName::System, "System"))
5248            .and_then(|e| e.create_sub_element(ElementName::FibexElements))
5249            .and_then(|e| e.create_sub_element(ElementName::FibexElementRefConditional))
5250            .and_then(|e| e.create_sub_element(ElementName::FibexElementRef))
5251            .unwrap();
5252        el_ref.set_relative_reference_target(target, base_label).unwrap();
5253        el_ref
5254    }
5255
5256    // helper for the rename/move tests: build a model in which the package "/Owner" declares the
5257    // reference base "Base" pointing at "/Target", and the sub-package "/Owner/Sub" contains a
5258    // relative reference which uses that base to refer to "/Target/Ecu".
5259    //
5260    // returns (model, /Owner, /Target, the relative reference element)
5261    fn build_relative_reference_model() -> (AutosarModel, Element, Element, Element) {
5262        let model = AutosarModel::new();
5263        model.create_file("test", AutosarVersion::LATEST).unwrap();
5264        let el_ar_packages = model
5265            .root_element()
5266            .create_sub_element(ElementName::ArPackages)
5267            .unwrap();
5268        let (el_target, el_ecu) = create_target_package(&el_ar_packages, "Target");
5269        let el_owner = el_ar_packages
5270            .create_named_sub_element(ElementName::ArPackage, "Owner")
5271            .unwrap();
5272        create_reference_base(&el_owner, "Base", &el_target);
5273        let el_owner_packages = el_owner.create_sub_element(ElementName::ArPackages).unwrap();
5274        let el_ref = create_relative_reference(&el_owner_packages, "Sub", &el_ecu, "Base");
5275
5276        // the reference is written as a relative path, and resolves through the reference base
5277        assert_eq!(el_ref.character_data().unwrap().string_value().unwrap(), "Ecu");
5278        assert_eq!(el_ref.get_reference_target().unwrap(), el_ecu);
5279        assert!(model.check_references().is_empty());
5280
5281        (model, el_owner, el_target, el_ref)
5282    }
5283
5284    #[test]
5285    fn rename_reference_base_owner_package() {
5286        // renaming the package which declares a reference base changes the scope in which the
5287        // reference base is visible, so the cached owner package path must be updated
5288        let (model, el_owner, _el_target, el_ref) = build_relative_reference_model();
5289        el_owner.set_item_name("Renamed").unwrap();
5290
5291        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Target"));
5292        // the reference has moved to /Renamed/Sub together with the reference base, so it still resolves
5293        assert_eq!(el_ref.get_reference_target().unwrap().path().unwrap(), "/Target/Ecu");
5294        assert!(model.check_references().is_empty());
5295        assert_eq!(model.verify_reference_caches(), Ok(()));
5296    }
5297
5298    #[test]
5299    fn rename_reference_base_target_package() {
5300        // renaming the package a reference base points at updates the PACKAGE-REF like any other
5301        // reference; the cached copy of the PACKAGE-REF must be updated to match
5302        let (model, el_owner, el_target, el_ref) = build_relative_reference_model();
5303        el_target.set_item_name("Renamed").unwrap();
5304
5305        let el_package_ref = get_package_ref(&el_owner);
5306        assert_eq!(
5307            el_package_ref.character_data().unwrap().string_value().unwrap(),
5308            "/Renamed"
5309        );
5310        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Renamed"));
5311        // the relative reference is unchanged, but now resolves to the renamed target
5312        assert_eq!(el_ref.character_data().unwrap().string_value().unwrap(), "Ecu");
5313        assert_eq!(el_ref.get_reference_target().unwrap().path().unwrap(), "/Renamed/Ecu");
5314        assert!(model.check_references().is_empty());
5315        assert_eq!(model.verify_reference_caches(), Ok(()));
5316    }
5317
5318    #[test]
5319    fn rename_reference_base_path_boundary() {
5320        // renaming /Pkg1 must not affect anything belonging to /Pkg10
5321        let model = AutosarModel::new();
5322        model.create_file("test", AutosarVersion::LATEST).unwrap();
5323        let el_ar_packages = model
5324            .root_element()
5325            .create_sub_element(ElementName::ArPackages)
5326            .unwrap();
5327        let (el_pkg1, _) = create_target_package(&el_ar_packages, "Pkg1");
5328        let (el_pkg10, _) = create_target_package(&el_ar_packages, "Pkg10");
5329        // both packages declare a reference base, each pointing at the other one
5330        create_reference_base(&el_pkg1, "Base1", &el_pkg10);
5331        create_reference_base(&el_pkg10, "Base10", &el_pkg1);
5332
5333        el_pkg1.set_item_name("Renamed").unwrap();
5334
5335        // each base is still declared by its own package and points at the other one; the PACKAGE-REF
5336        // of Base10 followed the rename of /Pkg1, and the one in /Pkg10 was not touched by it
5337        assert_eq!(
5338            get_package_ref(&el_pkg1).resolve_reference_base("Base1").as_deref(),
5339            Some("/Pkg10")
5340        );
5341        assert_eq!(get_package_ref(&el_pkg1).resolve_reference_base("Base10"), None);
5342        assert_eq!(
5343            get_package_ref(&el_pkg10).resolve_reference_base("Base10").as_deref(),
5344            Some("/Renamed")
5345        );
5346        assert_eq!(get_package_ref(&el_pkg10).resolve_reference_base("Base1"), None);
5347        assert_eq!(model.verify_reference_caches(), Ok(()));
5348    }
5349
5350    #[test]
5351    fn rename_short_name_of_reference_base_owner() {
5352        // Element::set_character_data on a SHORT-NAME renames an element without updating any
5353        // references to it. A reference base is found through the position of the REFERENCE-BASE in the
5354        // element tree, so renaming the package which declares it this way keeps it in scope.
5355        let (model, el_owner, el_target, el_ref) = build_relative_reference_model();
5356        el_owner
5357            .get_sub_element(ElementName::ShortName)
5358            .and_then(|e| e.set_character_data("Renamed").ok())
5359            .unwrap();
5360
5361        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Target"));
5362        assert_eq!(el_ref.get_reference_target().unwrap().path().unwrap(), "/Target/Ecu");
5363
5364        // The PACKAGE-REF of the reference base *is* a reference, so renaming its target this way
5365        // deliberately leaves it pointing at the old path. The cache must agree with the document,
5366        // which means the reference base - and the relative reference using it - now dangle.
5367        el_target
5368            .get_sub_element(ElementName::ShortName)
5369            .and_then(|e| e.set_character_data("MovedAway").ok())
5370            .unwrap();
5371
5372        // the PACKAGE-REF still contains "/Target", which no longer exists
5373        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Target"));
5374        assert!(el_ref.get_reference_target().is_err());
5375        // both the PACKAGE-REF, which still points at "/Target", and the relative reference which
5376        // resolves through it are now broken
5377        assert_eq!(model.check_references().len(), 2);
5378        assert_eq!(model.verify_reference_caches(), Ok(()));
5379    }
5380
5381    #[test]
5382    fn move_reference_base_owner_package() {
5383        // moving the package which declares a reference base must update the cached owner package path
5384        let (model, el_owner, el_target, el_ref) = build_relative_reference_model();
5385        el_target
5386            .create_sub_element(ElementName::ArPackages)
5387            .and_then(|e| e.move_element_here(&el_owner))
5388            .unwrap();
5389
5390        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Target"));
5391        // the relative reference moved along with the reference base, so it still resolves
5392        assert!(model.get_element_by_path("/Target/Owner/Sub/System").is_some());
5393        assert_eq!(el_ref.get_reference_target().unwrap().path().unwrap(), "/Target/Ecu");
5394        assert!(model.check_references().is_empty());
5395        assert_eq!(model.verify_reference_caches(), Ok(()));
5396    }
5397
5398    #[test]
5399    fn move_reference_base_target_package() {
5400        // moving the package a reference base points at updates the PACKAGE-REF like any other
5401        // reference; the cached copy of the PACKAGE-REF must be updated to match
5402        let (model, el_owner, el_target, el_ref) = build_relative_reference_model();
5403        el_owner
5404            .get_sub_element(ElementName::ArPackages)
5405            .unwrap()
5406            .move_element_here(&el_target)
5407            .unwrap();
5408
5409        assert_eq!(
5410            get_package_ref(&el_owner)
5411                .character_data()
5412                .unwrap()
5413                .string_value()
5414                .unwrap(),
5415            "/Owner/Target"
5416        );
5417        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Owner/Target"));
5418        assert_eq!(
5419            el_ref.get_reference_target().unwrap().path().unwrap(),
5420            "/Owner/Target/Ecu"
5421        );
5422        assert!(model.check_references().is_empty());
5423        assert_eq!(model.verify_reference_caches(), Ok(()));
5424    }
5425
5426    #[test]
5427    fn move_reference_bases_element_to_other_package() {
5428        // Moving a REFERENCE-BASES element to a different package changes the owner of every
5429        // reference base inside it. REFERENCE-BASES is not identifiable, so this cannot be handled
5430        // by remapping paths - the owning package does not move at all.
5431        let (model, el_owner, el_target, el_ref) = build_relative_reference_model();
5432        let el_reference_bases = el_owner.get_sub_element(ElementName::ReferenceBases).unwrap();
5433        el_target.move_element_here(&el_reference_bases).unwrap();
5434
5435        // the reference base is no longer in scope for the reference in /Owner/Sub
5436        assert_eq!(resolved_reference_base(&el_ref), None);
5437        assert!(el_ref.get_reference_target().is_err());
5438        assert_eq!(model.check_references().len(), 1);
5439        assert_eq!(model.verify_reference_caches(), Ok(()));
5440    }
5441
5442    #[test]
5443    fn move_single_reference_base_to_other_package() {
5444        // the same applies when a single REFERENCE-BASE is moved out of the REFERENCE-BASES element
5445        // of one package into that of another package
5446        let (model, el_owner, el_target, el_ref) = build_relative_reference_model();
5447        let el_reference_base = el_owner
5448            .get_sub_element(ElementName::ReferenceBases)
5449            .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
5450            .unwrap();
5451        el_target
5452            .create_sub_element(ElementName::ReferenceBases)
5453            .and_then(|e| e.move_element_here(&el_reference_base))
5454            .unwrap();
5455
5456        assert_eq!(resolved_reference_base(&el_ref), None);
5457        assert!(el_ref.get_reference_target().is_err());
5458
5459        // a relative reference inside /Target can use the reference base at its new location
5460        let el_target_packages = el_target.create_sub_element(ElementName::ArPackages).unwrap();
5461        let el_ecu = model.get_element_by_path("/Target/Ecu").unwrap();
5462        let el_new_ref = create_relative_reference(&el_target_packages, "Consumer", &el_ecu, "Base");
5463        assert_eq!(el_new_ref.get_reference_target().unwrap(), el_ecu);
5464        assert_eq!(model.verify_reference_caches(), Ok(()));
5465    }
5466
5467    #[test]
5468    fn move_unnamed_element_containing_packages() {
5469        // Moving a non-identifiable element which contains identifiable elements: one path remapping
5470        // entry is created per identifiable element directly inside the moved element. Everything
5471        // below such an element - further identifiable elements as well as reference bases - is
5472        // covered by that entry, so the collection loop does not need to descend into it.
5473        let model = AutosarModel::new();
5474        model.create_file("test", AutosarVersion::LATEST).unwrap();
5475        let el_ar_packages = model
5476            .root_element()
5477            .create_sub_element(ElementName::ArPackages)
5478            .unwrap();
5479        let (el_target, el_ecu) = create_target_package(&el_ar_packages, "Target");
5480        let el_dest = el_ar_packages
5481            .create_named_sub_element(ElementName::ArPackage, "Dest")
5482            .unwrap();
5483        let el_holder_packages = el_ar_packages
5484            .create_named_sub_element(ElementName::ArPackage, "Holder")
5485            .and_then(|e| e.create_sub_element(ElementName::ArPackages))
5486            .unwrap();
5487
5488        // PkgA declares a reference base and contains a sub-package which uses it
5489        let el_pkg_a = el_holder_packages
5490            .create_named_sub_element(ElementName::ArPackage, "PkgA")
5491            .unwrap();
5492        create_reference_base(&el_pkg_a, "BaseA", &el_target);
5493        let el_pkg_a_packages = el_pkg_a.create_sub_element(ElementName::ArPackages).unwrap();
5494        let el_ref = create_relative_reference(&el_pkg_a_packages, "PkgA_Sub", &el_ecu, "BaseA");
5495        // PkgB is a sibling of PkgA, and follows it in the element order
5496        let el_pkg_b = el_holder_packages
5497            .create_named_sub_element(ElementName::ArPackage, "PkgB")
5498            .unwrap();
5499        create_reference_base(&el_pkg_b, "BaseB", &el_target);
5500
5501        assert_eq!(el_ref.get_reference_target().unwrap(), el_ecu);
5502        assert!(model.get_element_by_path("/Holder/PkgA/PkgA_Sub").is_some());
5503
5504        // move the AR-PACKAGES element, which is not identifiable, into /Dest
5505        el_dest.move_element_here(&el_holder_packages).unwrap();
5506
5507        // the identifiable elements directly inside the moved element are remapped ...
5508        assert!(model.get_element_by_path("/Dest/PkgA").is_some());
5509        assert!(model.get_element_by_path("/Dest/PkgB").is_some());
5510        // ... and so are the identifiable elements nested below them, which the collection loop
5511        // never visits because it skips the subtree of PkgA
5512        assert!(model.get_element_by_path("/Dest/PkgA/PkgA_Sub").is_some());
5513        assert!(model.get_element_by_path("/Holder/PkgA").is_none());
5514        assert!(model.get_element_by_path("/Holder/PkgA/PkgA_Sub").is_none());
5515        assert!(model.get_element_by_path("/Holder/PkgB").is_none());
5516
5517        // the reference base inside the skipped subtree of PkgA still applies to the reference which
5518        // uses it, and BaseB proves that the sibling following PkgA is not skipped along with it
5519        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Target"));
5520        assert_eq!(
5521            get_package_ref(&el_pkg_b).resolve_reference_base("BaseB").as_deref(),
5522            Some("/Target")
5523        );
5524
5525        // the relative reference in the nested sub-package still resolves through BaseA
5526        assert_eq!(el_ref.get_reference_target().unwrap(), el_ecu);
5527        assert!(model.check_references().is_empty());
5528        assert_eq!(model.verify_reference_caches(), Ok(()));
5529    }
5530
5531    #[test]
5532    fn move_relative_reference_out_of_scope() {
5533        // A subtree containing relative references can be moved to a location where the reference
5534        // base is no longer in scope. The reference text is not rewritten, so the reference dangles.
5535        let (model, _el_owner, el_target, el_ref) = build_relative_reference_model();
5536        let el_sub = model.get_element_by_path("/Owner/Sub").unwrap();
5537        el_target
5538            .create_sub_element(ElementName::ArPackages)
5539            .and_then(|e| e.move_element_here(&el_sub))
5540            .unwrap();
5541
5542        assert_eq!(el_ref.character_data().unwrap().string_value().unwrap(), "Ecu");
5543        assert!(el_ref.get_reference_target().is_err());
5544        let broken = model.check_references();
5545        assert_eq!(broken.len(), 1);
5546        assert_eq!(broken[0].upgrade().unwrap(), el_ref);
5547        assert_eq!(model.verify_reference_caches(), Ok(()));
5548    }
5549
5550    #[test]
5551    fn move_relative_reference_into_new_scope() {
5552        // ... and if a reference base with the same label is in scope at the new location, then the
5553        // relative reference resolves through that one instead
5554        let (model, _el_owner, _el_target, el_ref) = build_relative_reference_model();
5555        let el_ar_packages = model.root_element().get_sub_element(ElementName::ArPackages).unwrap();
5556        // /Other declares the same base label, but points at a different package
5557        let (el_other_target, _) = create_target_package(&el_ar_packages, "OtherTarget");
5558        let el_other = el_ar_packages
5559            .create_named_sub_element(ElementName::ArPackage, "Other")
5560            .unwrap();
5561        create_reference_base(&el_other, "Base", &el_other_target);
5562
5563        let el_sub = model.get_element_by_path("/Owner/Sub").unwrap();
5564        el_other
5565            .create_sub_element(ElementName::ArPackages)
5566            .and_then(|e| e.move_element_here(&el_sub))
5567            .unwrap();
5568
5569        assert_eq!(el_ref.character_data().unwrap().string_value().unwrap(), "Ecu");
5570        assert_eq!(
5571            el_ref.get_reference_target().unwrap().path().unwrap(),
5572            "/OtherTarget/Ecu"
5573        );
5574        assert!(model.check_references().is_empty());
5575        assert_eq!(model.verify_reference_caches(), Ok(()));
5576    }
5577
5578    #[test]
5579    fn move_element_full_migrates_reference_bases() {
5580        // a cross-model move takes the reference bases along with the moved elements, so a relative
5581        // reference among them still resolves in the destination model
5582        let (model_src, el_owner, _el_target, el_ref) = build_relative_reference_model();
5583
5584        let model_dest = AutosarModel::new();
5585        model_dest.create_file("dest", AutosarVersion::LATEST).unwrap();
5586        let el_dest_packages = model_dest
5587            .root_element()
5588            .create_sub_element(ElementName::ArPackages)
5589            .unwrap();
5590        // the destination model gets its own copy of the target package, so that the reference base
5591        // still points at something after the move
5592        create_target_package(&el_dest_packages, "Target");
5593        let el_dest_outer = el_dest_packages
5594            .create_named_sub_element(ElementName::ArPackage, "Outer")
5595            .and_then(|e| e.create_sub_element(ElementName::ArPackages))
5596            .unwrap();
5597
5598        el_dest_outer.move_element_here(&el_owner).unwrap();
5599
5600        assert!(model_src.0.read().relative_references.is_empty());
5601        // the moved reference resolves through the moved reference base, in the destination model
5602        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Target"));
5603        // the relative reference is registered in the destination model and resolves there
5604        assert!(!model_dest.0.read().relative_references.is_empty());
5605        assert_eq!(el_ref.get_reference_target().unwrap().path().unwrap(), "/Target/Ecu");
5606        assert_eq!(model_dest.get_references_to("/Target/Ecu").len(), 1);
5607        assert!(model_dest.check_references().is_empty());
5608    }
5609
5610    #[test]
5611    fn move_element_full_keeps_external_references() {
5612        // a reference which points outside of the moved subtree keeps its text, but it must still be
5613        // registered in the destination model
5614        let model_src = AutosarModel::new();
5615        model_src.create_file("src", AutosarVersion::LATEST).unwrap();
5616        let el_src_packages = model_src
5617            .root_element()
5618            .create_sub_element(ElementName::ArPackages)
5619            .unwrap();
5620        let (_, el_src_ecu) = create_target_package(&el_src_packages, "Target");
5621        let el_ref = el_src_packages
5622            .create_named_sub_element(ElementName::ArPackage, "Sub")
5623            .and_then(|e| e.create_sub_element(ElementName::Elements))
5624            .and_then(|e| e.create_named_sub_element(ElementName::System, "System"))
5625            .and_then(|e| e.create_sub_element(ElementName::FibexElements))
5626            .and_then(|e| e.create_sub_element(ElementName::FibexElementRefConditional))
5627            .and_then(|e| e.create_sub_element(ElementName::FibexElementRef))
5628            .unwrap();
5629        el_ref.set_reference_target(&el_src_ecu).unwrap();
5630
5631        let model_dest = AutosarModel::new();
5632        model_dest.create_file("dest", AutosarVersion::LATEST).unwrap();
5633        let el_dest_packages = model_dest
5634            .root_element()
5635            .create_sub_element(ElementName::ArPackages)
5636            .unwrap();
5637        create_target_package(&el_dest_packages, "Target");
5638
5639        let el_sub = model_src.get_element_by_path("/Sub").unwrap();
5640        el_dest_packages.move_element_here(&el_sub).unwrap();
5641
5642        // the reference points outside the moved subtree, so its text is unchanged
5643        assert_eq!(el_ref.character_data().unwrap().string_value().unwrap(), "/Target/Ecu");
5644        // it must be findable in the destination model, where it resolves to the destination's own
5645        // element at that path
5646        assert_eq!(model_dest.get_references_to("/Target/Ecu").len(), 1);
5647        assert_eq!(model_src.get_references_to("/Target/Ecu").len(), 0);
5648        assert!(model_dest.check_references().is_empty());
5649    }
5650
5651    #[test]
5652    fn copy_resolves_relative_references() {
5653        // the copy of an element which contains both a REFERENCE-BASE and a relative reference using it
5654        // must resolve within the copy
5655        let (model, el_owner, _el_target, _el_ref) = build_relative_reference_model();
5656        let el_ar_packages = model.root_element().get_sub_element(ElementName::ArPackages).unwrap();
5657        let el_copy = el_ar_packages.create_copied_sub_element(&el_owner).unwrap();
5658
5659        assert_eq!(el_copy.path().unwrap(), "/Owner_1");
5660
5661        // the copied relative reference resolves through the copied reference base
5662        let el_copied_ref = model
5663            .get_element_by_path("/Owner_1/Sub/System")
5664            .and_then(|e| e.get_sub_element(ElementName::FibexElements))
5665            .and_then(|e| e.get_sub_element_at(0))
5666            .and_then(|e| e.get_sub_element(ElementName::FibexElementRef))
5667            .unwrap();
5668        assert_eq!(
5669            el_copied_ref.get_reference_target().unwrap().path().unwrap(),
5670            "/Target/Ecu"
5671        );
5672        assert_eq!(model.verify_reference_caches(), Ok(()));
5673    }
5674
5675    #[test]
5676    fn set_character_data_on_package_ref_retargets_reference_base() {
5677        // PACKAGE-REF is a reference element, so writing its content takes the reference branch of
5678        // set_character_data. The reference base declared by it must be updated anyway.
5679        let (model, el_owner, _el_target, el_ref) = build_relative_reference_model();
5680        let el_ar_packages = model.root_element().get_sub_element(ElementName::ArPackages).unwrap();
5681        create_target_package(&el_ar_packages, "OtherTarget");
5682
5683        assert_eq!(model.get_references_to("/Target/Ecu").len(), 1);
5684
5685        get_package_ref(&el_owner).set_character_data("/OtherTarget").unwrap();
5686
5687        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/OtherTarget"));
5688        assert_eq!(
5689            el_ref.get_reference_target().unwrap().path().unwrap(),
5690            "/OtherTarget/Ecu"
5691        );
5692        // retargeting the reference base changes what the relative reference points at without
5693        // changing its character data, so the reference has to be re-registered under the new path
5694        assert_eq!(model.get_references_to("/OtherTarget/Ecu").len(), 1);
5695        assert!(model.get_references_to("/Target/Ecu").is_empty());
5696        assert_eq!(model.verify_reference_caches(), Ok(()));
5697    }
5698
5699    #[test]
5700    fn set_base_attribute_on_package_ref_chains_reference_base() {
5701        // the BASE attribute of a PACKAGE-REF selects the reference base which the PACKAGE-REF
5702        // itself is relative to, so it is part of the cached declaration
5703        let (model, el_owner, el_target, _el_ref) = build_relative_reference_model();
5704        // add a second reference base in /Owner, whose PACKAGE-REF is relative to "Base"
5705        let el_inner = el_target
5706            .get_sub_element(ElementName::ArPackages)
5707            .unwrap_or_else(|| el_target.create_sub_element(ElementName::ArPackages).unwrap())
5708            .create_named_sub_element(ElementName::ArPackage, "Inner")
5709            .unwrap();
5710        create_reference_base(&el_owner, "Chained", &el_inner);
5711        let el_owner_package_ref = get_package_ref(&el_owner);
5712        assert_eq!(
5713            el_owner_package_ref.resolve_reference_base("Chained").as_deref(),
5714            Some("/Target/Inner")
5715        );
5716
5717        // rewrite the chained base to be relative to "Base"
5718        let el_chained_package_ref = el_owner
5719            .get_sub_element(ElementName::ReferenceBases)
5720            .and_then(|e| e.get_sub_element_at(1))
5721            .and_then(|e| e.get_sub_element(ElementName::PackageRef))
5722            .unwrap();
5723        el_chained_package_ref.set_character_data("Inner").unwrap();
5724        el_chained_package_ref
5725            .set_attribute_string(AttributeName::Base, "Base")
5726            .unwrap();
5727
5728        // the chained form leads to the same package as the absolute form did
5729        assert_eq!(
5730            el_owner_package_ref.resolve_reference_base("Chained").as_deref(),
5731            Some("/Target/Inner")
5732        );
5733
5734        // removing the BASE attribute leaves the relative text in place, so the declaration now names
5735        // the bogus path "Inner"
5736        assert!(el_chained_package_ref.remove_attribute(AttributeName::Base));
5737        assert_eq!(
5738            el_owner_package_ref.resolve_reference_base("Chained").as_deref(),
5739            Some("Inner")
5740        );
5741        assert_eq!(model.verify_reference_caches(), Ok(()));
5742    }
5743
5744    #[test]
5745    fn set_character_data_on_relative_reference() {
5746        // writing the content of a reference which already has a BASE attribute keeps the reference
5747        // relative: the new content is resolved against the same reference base, so the reference is
5748        // re-registered under the target path that results from combining the two
5749        let (model, _el_owner, el_target, el_ref) = build_relative_reference_model();
5750        let el_ecu2 = el_target
5751            .get_sub_element(ElementName::Elements)
5752            .unwrap()
5753            .create_named_sub_element(ElementName::EcuInstance, "Ecu2")
5754            .unwrap();
5755
5756        el_ref.set_character_data("Ecu2").unwrap();
5757
5758        assert_eq!(
5759            el_ref
5760                .attribute_value(AttributeName::Base)
5761                .unwrap()
5762                .string_value()
5763                .unwrap(),
5764            "Base"
5765        );
5766        assert_eq!(el_ref.get_reference_target().unwrap(), el_ecu2);
5767        assert_eq!(
5768            model.relative_reference_target(&el_ref.downgrade()).as_deref(),
5769            Some("/Target/Ecu2")
5770        );
5771        assert_eq!(model.get_references_to("/Target/Ecu2").len(), 1);
5772        assert!(model.get_references_to("/Target/Ecu").is_empty());
5773        assert!(model.check_references().is_empty());
5774        assert_eq!(model.verify_reference_caches(), Ok(()));
5775    }
5776
5777    #[test]
5778    fn set_character_data_incompatible_value() {
5779        // a value which is not compatible with the character data spec is rejected
5780        let model = AutosarModel::new();
5781        model.create_file("test", AutosarVersion::LATEST).unwrap();
5782        let el_elements = model
5783            .root_element()
5784            .create_sub_element(ElementName::ArPackages)
5785            .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
5786            .and_then(|e| e.create_sub_element(ElementName::Elements))
5787            .unwrap();
5788        let el_ecu_instance = el_elements
5789            .create_named_sub_element(ElementName::EcuInstance, "Ecu")
5790            .unwrap();
5791        let el_sleep_mode_supported = el_ecu_instance
5792            .create_sub_element(ElementName::SleepModeSupported)
5793            .unwrap();
5794
5795        // SLEEP-MODE-SUPPORTED contains a boolean, so an arbitrary string is not valid
5796        assert!(matches!(
5797            el_sleep_mode_supported.set_character_data("nonsense"),
5798            Err(AutosarDataError::InvalidCharacterData {
5799                element: ElementName::SleepModeSupported,
5800                ..
5801            })
5802        ));
5803        assert!(el_sleep_mode_supported.character_data().is_none());
5804
5805        // a valid value is accepted
5806        el_sleep_mode_supported.set_character_data(true).unwrap();
5807        assert_eq!(
5808            el_sleep_mode_supported
5809                .character_data()
5810                .unwrap()
5811                .string_value()
5812                .unwrap(),
5813            "true"
5814        );
5815
5816        // ABSOLUTE contains a floating point number. Unlike the String and Pattern specs, there is no
5817        // fallback which converts the value to a plain string, so a string value is rejected.
5818        let el_absolute = el_elements
5819            .create_named_sub_element(ElementName::ISignalIPdu, "Pdu")
5820            .and_then(|e| e.create_sub_element(ElementName::IPduTimingSpecifications))
5821            .and_then(|e| e.create_sub_element(ElementName::IPduTiming))
5822            .and_then(|e| e.create_sub_element(ElementName::TransmissionModeDeclaration))
5823            .and_then(|e| e.create_sub_element(ElementName::TransmissionModeTrueTiming))
5824            .and_then(|e| e.create_sub_element(ElementName::CyclicTiming))
5825            .and_then(|e| e.create_sub_element(ElementName::TimePeriod))
5826            .and_then(|e| e.create_sub_element(ElementName::Tolerance))
5827            .and_then(|e| e.create_sub_element(ElementName::AbsoluteTolerance))
5828            .and_then(|e| e.create_sub_element(ElementName::Absolute))
5829            .unwrap();
5830        assert!(matches!(
5831            el_absolute.set_character_data("not a number"),
5832            Err(AutosarDataError::InvalidCharacterData {
5833                element: ElementName::Absolute,
5834                ..
5835            })
5836        ));
5837        el_absolute.set_character_data(1.5).unwrap();
5838        assert_eq!(el_absolute.character_data(), Some(CharacterData::Float(1.5)));
5839    }
5840
5841    #[test]
5842    fn debug_format_of_elements() {
5843        let model = AutosarModel::new();
5844        model.create_file("test", AutosarVersion::LATEST).unwrap();
5845        let el_ar_package = model
5846            .root_element()
5847            .create_sub_element(ElementName::ArPackages)
5848            .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
5849            .unwrap();
5850        let el_short_name = el_ar_package.get_sub_element(ElementName::ShortName).unwrap();
5851
5852        // an identifiable element shows its name, and its parent is another element
5853        let package_text = format!("{el_ar_package:#?}");
5854        assert!(package_text.contains(r#"name: "Pkg""#));
5855        assert!(package_text.contains("parent: Element"));
5856
5857        // the content of the SHORT-NAME is character data
5858        let short_name_text = format!("{el_short_name:#?}");
5859        assert!(short_name_text.contains("Pkg"));
5860
5861        // the parent of the root element is the model
5862        assert!(format!("{:#?}", model.root_element()).contains("parent: Model"));
5863
5864        // a deleted element has no parent at all, and its weak reference can't be upgraded any more
5865        let weak_short_name = el_short_name.downgrade();
5866        model
5867            .root_element()
5868            .get_sub_element(ElementName::ArPackages)
5869            .unwrap()
5870            .remove_sub_element(el_ar_package)
5871            .unwrap();
5872        assert!(format!("{el_short_name:#?}").contains("parent: None/Invalid"));
5873        drop(el_short_name);
5874        assert!(format!("{weak_short_name:#?}").contains("(invalid)"));
5875    }
5876
5877    #[test]
5878    fn reference_base_with_additional_sub_elements() {
5879        // a REFERENCE-BASE may contain more than just SHORT-LABEL and PACKAGE-REF; the additional
5880        // sub elements are not part of the declaration and must be skipped when it is read
5881        let (model, el_owner, _el_target, el_ref) = build_relative_reference_model();
5882        let el_reference_base = el_owner
5883            .get_sub_element(ElementName::ReferenceBases)
5884            .and_then(|e| e.get_sub_element(ElementName::ReferenceBase))
5885            .unwrap();
5886        el_reference_base
5887            .create_sub_element(ElementName::IsDefault)
5888            .and_then(|e| e.set_character_data(true))
5889            .unwrap();
5890        el_reference_base
5891            .create_sub_element(ElementName::IsGlobal)
5892            .and_then(|e| e.set_character_data(false))
5893            .unwrap();
5894
5895        assert_eq!(resolved_reference_base(&el_ref).as_deref(), Some("/Target"));
5896        assert_eq!(el_ref.get_reference_target().unwrap().path().unwrap(), "/Target/Ecu");
5897        assert!(model.check_references().is_empty());
5898        assert_eq!(model.verify_reference_caches(), Ok(()));
5899    }
5900
5901    #[test]
5902    fn copy_drops_incompatible_optional_attribute() {
5903        // copying into a file of an older version filters out everything that is not valid there.
5904        // An optional attribute which does not exist in the target version is simply omitted.
5905        let model_new = AutosarModel::new();
5906        model_new.create_file("new", AutosarVersion::LATEST).unwrap();
5907        let el_package_new = model_new
5908            .root_element()
5909            .create_sub_element(ElementName::ArPackages)
5910            .and_then(|e| e.create_named_sub_element(ElementName::ArPackage, "Pkg"))
5911            .unwrap();
5912        let el_short_label = el_package_new
5913            .create_sub_element(ElementName::VariationPoint)
5914            .and_then(|e| e.create_sub_element(ElementName::ShortLabel))
5915            .unwrap();
5916        el_short_label.set_character_data("Label").unwrap();
5917        el_short_label
5918            .set_attribute_string(AttributeName::BlueprintValue, "bpv")
5919            .unwrap();
5920
5921        let model_old = AutosarModel::new();
5922        model_old.create_file("old", AutosarVersion::Autosar_4_3_0).unwrap();
5923        let el_copy = model_old
5924            .root_element()
5925            .create_sub_element(ElementName::ArPackages)
5926            .and_then(|e| e.create_copied_sub_element(&el_package_new))
5927            .unwrap();
5928
5929        let el_short_label_copy = el_copy
5930            .get_sub_element(ElementName::VariationPoint)
5931            .and_then(|e| e.get_sub_element(ElementName::ShortLabel))
5932            .unwrap();
5933        assert_eq!(
5934            el_short_label_copy.character_data().unwrap().string_value().unwrap(),
5935            "Label"
5936        );
5937        // BLUEPRINT-VALUE does not exist in Autosar_4_3_0, and it is optional, so it was dropped
5938        assert!(
5939            el_short_label_copy
5940                .attribute_value(AttributeName::BlueprintValue)
5941                .is_none()
5942        );
5943    }
5944}