1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
use std::path::Path;

use crate::*;

impl ArxmlFile {
    pub(crate) fn new<P: AsRef<Path>>(filename: P, version: AutosarVersion, container: &AutosarProject) -> Self {
        let xsi_schemalocation =
            CharacterData::String(format!("http://autosar.org/schema/r4.0 {}", version.filename()));
        let xmlns = CharacterData::String("http://autosar.org/schema/r4.0".to_string());
        let xmlns_xsi = CharacterData::String("http://www.w3.org/2001/XMLSchema-instance".to_string());
        let root_attributes = smallvec::smallvec![
            Attribute {
                attrname: AttributeName::xsiSchemalocation,
                content: xsi_schemalocation
            },
            Attribute {
                attrname: AttributeName::xmlns,
                content: xmlns
            },
            Attribute {
                attrname: AttributeName::xmlnsXsi,
                content: xmlns_xsi
            },
        ];
        let root_element = Element(Arc::new(Mutex::new(ElementRaw {
            parent: ElementOrFile::None,
            elemname: ElementName::Autosar,
            elemtype: ElementType::ROOT,
            content: SmallVec::new(),
            attributes: root_attributes,
        })));
        let file = Self(Arc::new(Mutex::new(ArxmlFileRaw {
            project: container.downgrade(),
            root_element,
            version,
            filename: filename.as_ref().to_path_buf(),
        })));
        let new_parent = ElementOrFile::File(file.downgrade());
        file.root_element().set_parent(new_parent);
        file
    }

    /// Get the filename of this ArxmlFile
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// println!("filename is : {}", file.filename().display());
    /// ```
    pub fn filename(&self) -> PathBuf {
        self.0.lock().filename.clone()
    }

    /// Set the filename of this arxml filename
    ///
    /// This will not rename any existing file on disk, but the new filename will be used when writing the data.
    ///
    /// # Example
    ///
    /// ```
    /// # use std::path::Path;
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// file.set_filename("foo.arxml");
    /// // or
    /// file.set_filename(&Path::new("bar.arxml"));
    /// ```
    pub fn set_filename<P: AsRef<Path>>(&self, new_filename: P) {
        self.0.lock().filename = new_filename.as_ref().to_path_buf();
    }

    /// Get the [AutosarVersion] of the file
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// let version = file.version();
    /// ```
    pub fn version(&self) -> AutosarVersion {
        self.0.lock().version
    }

    /// Set the [AutosarVersion] of the file
    ///
    /// The compatibility of the data in the file with the new version will be checked before setting the version.
    /// The compatibility check can also be performed manually using the function `check_version_compatibility()`.
    ///
    /// If the data is compatible, then the version is set, otherwise an error is raised.
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// file.set_version(AutosarVersion::Autosar_00050);
    /// ```
    ///
    /// # Possible Errors
    ///
    ///  - [AutosarDataError::VersionIncompatible] the existing data is not compatible with the new version
    ///
    pub fn set_version(&self, new_ver: AutosarVersion) -> Result<(), AutosarDataError> {
        let (compat_errors, _) = self.check_version_compatibility(new_ver);
        if compat_errors.is_empty() {
            let mut file = self.0.lock();
            file.version = new_ver;
            let attribute_value =
                CharacterData::String(format!("http://autosar.org/schema/r4.0 {}", new_ver.filename()));
            file.root_element.0.lock().set_attribute_internal(
                AttributeName::xsiSchemalocation,
                attribute_value,
                new_ver,
            );
            Ok(())
        } else {
            Err(AutosarDataError::VersionIncompatible)
        }
    }

    /// Check if the elements and attributes in this file are compatible with some target_version
    ///
    /// All elements and their attributes will be evaluated against the target version according to the specification.
    /// The output is a list of incompatible elements
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// let (error_list, compat_mask) = file.check_version_compatibility(AutosarVersion::Autosar_00050);
    /// ```
    pub fn check_version_compatibility(&self, target_version: AutosarVersion) -> (Vec<CompatibilityError>, u32) {
        self.root_element().check_version_compatibility(target_version)
    }

    /// Get a reference to the [AutosarProject] object that contains this file
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # fn main() -> Result<(), AutosarDataError> {
    /// let project = AutosarProject::new();
    /// let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// let p2 = file.project()?;
    /// assert_eq!(project, p2);
    /// # Ok(())
    /// # }
    /// ```
    ///
    /// # Possible Errors
    ///
    /// [AutosarDataError::ItemDeleted]: The project is no longer valid
    ///
    pub fn project(&self) -> Result<AutosarProject, AutosarDataError> {
        let locked_file = self.0.lock();
        // This reference must always be valid, so it is an error if upgrade() fails
        locked_file.project.upgrade().ok_or(AutosarDataError::ItemDeleted)
    }

    /// Get a referenct to the root ```<AUTOSAR ...>``` element of this file
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// let autosar_element = file.root_element();
    /// ```
    pub fn root_element(&self) -> Element {
        let file = self.0.lock();
        file.root_element.clone()
    }

    /// Create a depth-first search iterator over all [Element]s in this file
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// for (depth, elem) in file.elements_dfs() {
    ///     // ...
    /// }
    /// ```
    pub fn elements_dfs(&self) -> ElementsDfsIterator {
        let file = self.0.lock();
        file.root_element.elements_dfs()
    }

    /// Serialize the content of the file to a String
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// let text = file.serialize();
    /// ```
    pub fn serialize(&self) -> String {
        let mut outstring = String::with_capacity(1024 * 1024);

        outstring.push_str("<?xml version=\"1.0\" encoding=\"utf-8\"?>");
        self.root_element().serialize_internal(&mut outstring, 0, false);

        outstring
    }

    /// Create a weak reference to this ArxmlFile
    ///
    /// A weak reference can be stored without preventing the file from being deallocated.
    /// The weak reference has to be upgraded in order to be used, which can fail if the file no longer exists.
    ///
    /// See the documentation for [Arc]
    ///
    /// # Example
    ///
    /// ```
    /// # use autosar_data::*;
    /// # let project = AutosarProject::new();
    /// # let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
    /// let weak_file = file.downgrade();
    /// ```
    pub fn downgrade(&self) -> WeakArxmlFile {
        WeakArxmlFile(Arc::downgrade(&self.0))
    }
}

impl PartialEq for ArxmlFile {
    fn eq(&self, other: &Self) -> bool {
        Arc::as_ptr(&self.0) == Arc::as_ptr(&other.0)
    }
}

impl WeakArxmlFile {
    /// try to get a strong reference to the [ArxmlFile]
    ///
    /// This succeeds if the ArxmlFile still has any other strong reference to it, otherwise None is returned
    pub fn upgrade(&self) -> Option<ArxmlFile> {
        Weak::upgrade(&self.0).map(ArxmlFile)
    }
}

impl PartialEq for WeakArxmlFile {
    fn eq(&self, other: &Self) -> bool {
        Weak::as_ptr(&self.0) == Weak::as_ptr(&other.0)
    }
}

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn create() {
        let project = AutosarProject::new();
        let result = project.create_file("test", AutosarVersion::Autosar_4_0_1);
        assert!(result.is_ok());
    }

    #[test]
    fn filename() {
        let project = AutosarProject::new();
        let result = project.create_file("test", AutosarVersion::Autosar_4_0_1);
        let file = result.unwrap();
        let filename = PathBuf::from("newname.arxml");
        file.set_filename(filename.clone());
        assert_eq!(file.filename(), filename);
    }

    #[test]
    fn version() {
        let project = AutosarProject::new();
        let result = project.create_file("test", AutosarVersion::Autosar_4_0_1);
        let file = result.unwrap();
        file.set_version(AutosarVersion::Autosar_00050).unwrap();
        assert_eq!(file.version(), AutosarVersion::Autosar_00050);
    }

    #[test]
    fn references() {
        let project = AutosarProject::new();
        let result = project.create_file("test", AutosarVersion::Autosar_4_0_1);
        let file = result.unwrap();
        let weak_file = file.downgrade();
        let file2 = weak_file.upgrade().unwrap();
        assert_eq!(Arc::strong_count(&file.0), 3); // 3 references are: AutosarProject, file, file2
        assert_eq!(file, file2);
    }

    #[test]
    fn serialize() {
        let project = AutosarProject::new();
        let file = project.create_file("test", AutosarVersion::Autosar_00050).unwrap();
        let text = file.serialize();
        assert_eq!(
            text,
            r#"<?xml version="1.0" encoding="utf-8"?>
<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">
</AUTOSAR>"#
        );
    }
}