Skip to content

Commit 85aab3c

Browse files
committed
Fix incorrect model merge
In some cases elements that were not identical were merged, because the character data content was not checked. The merge may need to backtrack to a parent element to handle this. Fixes issue #30
1 parent d997780 commit 85aab3c

3 files changed

Lines changed: 148 additions & 32 deletions

File tree

autosar-data/src/autosarmodel.rs

Lines changed: 142 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ use std::{collections::HashMap, hash::Hash};
22

33
use crate::*;
44

5+
#[derive(Debug)]
56
// actions to be taken when merging two elements
67
enum MergeAction {
78
MergeEqual,
@@ -232,7 +233,17 @@ impl AutosarModel {
232233
let root = self.root_element();
233234
let files: HashSet<WeakArxmlFile> = self.files().map(|f| f.downgrade()).collect();
234235

235-
Self::merge_element(&root, &files, new_root, &new_file)?;
236+
Self::merge_element(&root, &files, new_root, &new_file).map_err(|e| {
237+
// transform ElementInsertionConflict into InvalidFileMerge
238+
if let AutosarDataError::ElementInsertionConflict { parent_path, .. } = &e {
239+
AutosarDataError::InvalidFileMerge {
240+
path: parent_path.clone(),
241+
}
242+
} else {
243+
e
244+
}
245+
})?;
246+
236247
self.root_element().0.write().file_membership.insert(new_file);
237248

238249
Ok(())
@@ -340,11 +351,13 @@ impl AutosarModel {
340351
files.clone_into(&mut elem_locked.file_membership);
341352
}
342353
}
354+
343355
// elements in elements_b_only are not present in the model yet, so they need to be added
356+
// this step can fail, in which case the merge of this element fails
344357
Self::import_new_items(parent_a, elements_b_only, new_file, min_ver_b)?;
345358

346359
// recurse for sub elements that are present on both sides: these need to be checked and merged
347-
Self::merge_sub_elements(elements_merge, files, new_file)?;
360+
Self::merge_sub_elements(parent_a, elements_merge, files, new_file, version)?;
348361

349362
Ok(())
350363
}
@@ -400,9 +413,15 @@ impl AutosarModel {
400413

401414
if defref_a == defref_b {
402415
// either: defrefs exist and are identical, OR they are both None.
403-
// if they are None, then there is nothing else that can be compared, so we just assume the elements are identical.
404-
// Merge them and advance both iterators.
405-
MergeAction::MergeEqual
416+
if elem_a.character_data() != elem_b.character_data() {
417+
// they have different character data, so they are not identical
418+
// take only a, defer b
419+
MergeAction::AOnly
420+
} else {
421+
// if they are None, then there is nothing else that can be compared, so we just assume the elements are identical.
422+
// Merge them and advance both iterators.
423+
MergeAction::MergeEqual
424+
}
406425
} else {
407426
// check if a sibling of elem_b has the same definiton-ref as elem_a
408427
// this handles the case where the the elements on both sides are ordered differently
@@ -430,41 +449,53 @@ impl AutosarModel {
430449
parent_a: &Element,
431450
elements_b_only: Vec<(Element, usize)>,
432451
new_file: &WeakArxmlFile,
433-
min_ver_b: AutosarVersion,
452+
version: AutosarVersion,
434453
) -> Result<(), AutosarDataError> {
435454
// elements in elements_b_only are not present in the model yet, so they need to be added
436-
let mut parent_a_locked = parent_a.0.write();
437455
for (idx, (new_element, insert_pos)) in elements_b_only.into_iter().enumerate() {
438-
new_element.set_parent(ElementOrModel::Element(parent_a.downgrade()));
439-
// restrict new_element, it is only present in new_file
440-
new_element.0.write().file_membership.insert(new_file.clone());
441-
442-
// add the new_element (from side b) to the content of parent_a
443-
// to do this, first check valid element insertion positions
444-
let (first_pos, last_pos) = parent_a_locked
445-
.calc_element_insert_range(new_element.element_name(), min_ver_b)
446-
.map_err(|_| AutosarDataError::InvalidFileMerge {
447-
path: new_element.element_name().to_string(),
448-
})?;
449-
450456
// idx number of elements have already been inserted, so the destination position must be adjusted
451457
let dest = insert_pos + idx;
452458

453-
// clamp dest, so that first_pos <= dest <= last_pos
454-
let dest = dest.max(first_pos).min(last_pos);
455-
456-
// insert the element from b at the calculated position
457-
parent_a_locked
458-
.content
459-
.insert(dest, ElementContent::Element(new_element));
459+
Self::import_single_item(parent_a, new_element, dest, new_file, version)?;
460460
}
461461
Ok(())
462462
}
463463

464+
fn import_single_item(
465+
parent_a: &Element,
466+
new_element: Element,
467+
dest: usize,
468+
new_file: &WeakArxmlFile,
469+
version: AutosarVersion,
470+
) -> Result<(), AutosarDataError> {
471+
let mut parent_a_locked = parent_a.0.write();
472+
let weak_parent_a = parent_a.downgrade();
473+
474+
new_element.set_parent(ElementOrModel::Element(weak_parent_a));
475+
// restrict new_element, it is only present in new_file
476+
new_element.0.write().file_membership.insert(new_file.clone());
477+
478+
// add the new_element (from side b) to the content of parent_a
479+
// to do this, first check valid element insertion positions
480+
let (first_pos, last_pos) = parent_a_locked.calc_element_insert_range(new_element.element_name(), version)?;
481+
482+
// clamp dest, so that first_pos <= dest <= last_pos
483+
let dest = dest.max(first_pos).min(last_pos);
484+
485+
// insert the element from b at the calculated position
486+
parent_a_locked
487+
.content
488+
.insert(dest, ElementContent::Element(new_element));
489+
490+
Ok(())
491+
}
492+
464493
fn merge_sub_elements(
494+
parent_a: &Element,
465495
elements_merge: Vec<(Element, Element)>,
466496
files: &HashSet<WeakArxmlFile>,
467497
new_file: &WeakArxmlFile,
498+
version: AutosarVersion,
468499
) -> Result<(), AutosarDataError> {
469500
for (elem_a, elem_b) in elements_merge {
470501
// get the list of files that the element from a is present in
@@ -475,12 +506,34 @@ impl AutosarModel {
475506
};
476507

477508
// merge the two elements
478-
AutosarModel::merge_element(&elem_a, &files, &elem_b, new_file)?;
509+
let result = AutosarModel::merge_element(&elem_a, &files, &elem_b, new_file);
510+
match result {
511+
Ok(()) => {
512+
// update the file membership of the merged element, if there was any
513+
let mut elem_a_locked = elem_a.0.write();
514+
if !elem_a_locked.file_membership.is_empty() {
515+
elem_a_locked.file_membership.insert(new_file.clone());
516+
}
517+
}
518+
Err(e) => {
519+
if let AutosarDataError::ElementInsertionConflict { parent_path, .. } = &e {
520+
// failed to merge sub element due to insertion conflict
521+
if elem_a.is_identifiable() {
522+
// if the merge of an identifiable element fails, then the whole merge fails
523+
return Err(AutosarDataError::InvalidFileMerge {
524+
path: parent_path.clone(),
525+
});
526+
} else if parent_a.element_type().splittable_in(version) {
527+
elem_a.set_file_membership(files);
528+
// try to import elem_b as a new item instead
529+
let dest = elem_a.position().unwrap_or_default() + 1;
530+
return Self::import_single_item(parent_a, elem_b, dest, new_file, version).map_err(|_| e);
531+
}
532+
}
479533

480-
// update the file membership of the merged element, if there was any
481-
let mut elem_a_locked = elem_a.0.write();
482-
if !elem_a_locked.file_membership.is_empty() {
483-
elem_a_locked.file_membership.insert(new_file.clone());
534+
// propagate the error back to the parent, perhaps it can be handled there
535+
return Err(e);
536+
}
484537
}
485538
}
486539
Ok(())
@@ -1734,4 +1787,62 @@ mod test {
17341787
println!("{}\n\n=======================\n{}\n\n", model_txt, model2_txt);
17351788
assert_eq!(model_txt, model2_txt);
17361789
}
1790+
1791+
#[test]
1792+
fn model_merge_2() {
1793+
// regression test for github issue #30
1794+
const FILEBUF1: &[u8] = br#"<?xml version="1.0" encoding="UTF-8"?>
1795+
<AUTOSAR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://autosar.org/schema/r4.0" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-3-0.xsd">
1796+
<AR-PACKAGES>
1797+
<AR-PACKAGE>
1798+
<SHORT-NAME>BSWMD_Package</SHORT-NAME>
1799+
<ELEMENTS>
1800+
<BSW-MODULE-DESCRIPTION>
1801+
<SHORT-NAME>BSWMD</SHORT-NAME>
1802+
<IMPLEMENTED-ENTRYS>
1803+
<BSW-MODULE-ENTRY-REF-CONDITIONAL>
1804+
<BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_A0</BSW-MODULE-ENTRY-REF>
1805+
</BSW-MODULE-ENTRY-REF-CONDITIONAL>
1806+
</IMPLEMENTED-ENTRYS>
1807+
</BSW-MODULE-DESCRIPTION>
1808+
</ELEMENTS>
1809+
</AR-PACKAGE>
1810+
</AR-PACKAGES>
1811+
</AUTOSAR>"#;
1812+
const FILEBUF2: &[u8] = br#"<?xml version="1.0" encoding="UTF-8"?>
1813+
<AUTOSAR xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://autosar.org/schema/r4.0" xsi:schemaLocation="http://autosar.org/schema/r4.0 AUTOSAR_4-3-0.xsd">
1814+
<AR-PACKAGES>
1815+
<AR-PACKAGE>
1816+
<SHORT-NAME>BSWMD_Package</SHORT-NAME>
1817+
<ELEMENTS>
1818+
<BSW-MODULE-DESCRIPTION>
1819+
<SHORT-NAME>BSWMD</SHORT-NAME>
1820+
<IMPLEMENTED-ENTRYS>
1821+
<BSW-MODULE-ENTRY-REF-CONDITIONAL>
1822+
<BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_B0</BSW-MODULE-ENTRY-REF>
1823+
</BSW-MODULE-ENTRY-REF-CONDITIONAL>
1824+
<BSW-MODULE-ENTRY-REF-CONDITIONAL>
1825+
<BSW-MODULE-ENTRY-REF DEST="BSW-MODULE-ENTRY">/path/to/entry_B1</BSW-MODULE-ENTRY-REF>
1826+
</BSW-MODULE-ENTRY-REF-CONDITIONAL>
1827+
</IMPLEMENTED-ENTRYS>
1828+
</BSW-MODULE-DESCRIPTION>
1829+
</ELEMENTS>
1830+
</AR-PACKAGE>
1831+
</AR-PACKAGES>
1832+
</AUTOSAR>"#;
1833+
1834+
let model = AutosarModel::new();
1835+
let (_, _) = model.load_buffer(FILEBUF1, "file1", true).unwrap();
1836+
let (_, _) = model.load_buffer(FILEBUF2, "file2", true).unwrap();
1837+
1838+
let a0_refs = model.get_references_to("/path/to/entry_A0");
1839+
assert!(a0_refs.len() == 1);
1840+
assert!(a0_refs[0].upgrade().is_some());
1841+
let b0_refs = model.get_references_to("/path/to/entry_B0");
1842+
assert!(b0_refs.len() == 1);
1843+
assert!(b0_refs[0].upgrade().is_some());
1844+
let b1_refs = model.get_references_to("/path/to/entry_B1");
1845+
assert!(b1_refs.len() == 1);
1846+
assert!(b1_refs[0].upgrade().is_some());
1847+
}
17371848
}

autosar-data/src/elementraw.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1016,6 +1016,7 @@ impl ElementRaw {
10161016
return Err(AutosarDataError::ElementInsertionConflict {
10171017
parent: self.element_name(),
10181018
element: element_name,
1019+
parent_path: self.xml_path(),
10191020
});
10201021
}
10211022
}
@@ -1040,6 +1041,7 @@ impl ElementRaw {
10401041
return Err(AutosarDataError::ElementInsertionConflict {
10411042
parent: self.element_name(),
10421043
element: element_name,
1044+
parent_path: self.xml_path(),
10431045
});
10441046
}
10451047
}
@@ -1051,6 +1053,7 @@ impl ElementRaw {
10511053
return Err(AutosarDataError::ElementInsertionConflict {
10521054
parent: self.element_name(),
10531055
element: element_name,
1056+
parent_path: self.xml_path(),
10541057
});
10551058
}
10561059
}

autosar-data/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -253,12 +253,14 @@ pub enum AutosarDataError {
253253
},
254254

255255
/// Could not insert a sub element, because it conflicts with an existing sub element
256-
#[error("Element insertion conflict: {} could not be inserted in {}", .element, .parent)]
256+
#[error("Element insertion conflict: {} could not be inserted in {} ({})", .element, .parent, .parent_path)]
257257
ElementInsertionConflict {
258258
/// The name of the parent element
259259
parent: ElementName,
260260
/// The name of the element that could not be inserted
261261
element: ElementName,
262+
/// path of the parent element
263+
parent_path: String,
262264
},
263265

264266
/// The `ElementName` is not a valid sub element according to the specification.

0 commit comments

Comments
 (0)