Skip to main content

xml_3dm/merge/
merge_list.rs

1//! Merge list structures for the 3-way merge algorithm.
2//!
3//! A MergeList represents the children of a branch node organized for merging.
4//! Each entry corresponds to a child, with additional "hangon" nodes that
5//! represent inserts or copies that occur after that position.
6
7use std::collections::HashMap;
8
9use crate::node::{BranchNode, NodeRef};
10
11use super::edit_log::EditLog;
12
13/// A single entry in a merge list.
14///
15/// An entry contains a node and any "hangon" nodes that should be
16/// placed after it in the merged output. Hangons are typically:
17/// - Newly inserted nodes
18/// - Nodes copied from elsewhere in the tree
19/// - Far-moved nodes (moved from a different parent)
20#[derive(Debug, Clone)]
21pub struct MergeEntry {
22    /// The main node for this entry, or None for START/END markers.
23    node: Option<NodeRef>,
24    /// Whether this is the START marker.
25    pub is_start: bool,
26    /// Whether this is the END marker.
27    pub is_end: bool,
28    /// Hangon nodes (inserts/copies after this position).
29    hangons: Vec<NodeRef>,
30    /// Whether this entry is locked (cannot be freely reordered).
31    pub locked: bool,
32    /// Whether this node was moved within the child list.
33    pub moved: bool,
34    /// Merge partner from the other branch (set during merge).
35    merge_partner: Option<NodeRef>,
36}
37
38impl MergeEntry {
39    /// Creates a new entry with the given node.
40    pub fn new(node: Option<NodeRef>) -> Self {
41        MergeEntry {
42            node,
43            is_start: false,
44            is_end: false,
45            hangons: Vec::new(),
46            locked: false,
47            moved: false,
48            merge_partner: None,
49        }
50    }
51
52    /// Creates the START marker entry.
53    pub fn start() -> Self {
54        MergeEntry {
55            node: None,
56            is_start: true,
57            is_end: false,
58            hangons: Vec::new(),
59            locked: false,
60            moved: false,
61            merge_partner: None,
62        }
63    }
64
65    /// Creates the END marker entry.
66    pub fn end() -> Self {
67        MergeEntry {
68            node: None,
69            is_start: false,
70            is_end: true,
71            hangons: Vec::new(),
72            locked: false,
73            moved: false,
74            merge_partner: None,
75        }
76    }
77
78    /// Returns the node for this entry.
79    pub fn node(&self) -> Option<NodeRef> {
80        self.node.clone()
81    }
82
83    /// Returns the hangon nodes.
84    pub fn hangons(&self) -> &[NodeRef] {
85        &self.hangons
86    }
87
88    /// Adds a hangon node.
89    pub fn add_hangon(&mut self, node: NodeRef) {
90        self.hangons.push(node);
91    }
92
93    /// Returns the number of hangon nodes.
94    pub fn hangon_count(&self) -> usize {
95        self.hangons.len()
96    }
97
98    /// Sets the merge partner.
99    pub fn set_merge_partner(&mut self, partner: Option<NodeRef>) {
100        self.merge_partner = partner;
101    }
102
103    /// Returns the merge partner.
104    pub fn merge_partner(&self) -> Option<&NodeRef> {
105        self.merge_partner.as_ref()
106    }
107
108    /// Returns the base match of this entry's node (if any).
109    pub fn base_match(&self) -> Option<NodeRef> {
110        self.node.as_ref().and_then(BranchNode::base_match)
111    }
112}
113
114/// A list of merge entries for a branch node's children.
115///
116/// The merge list is used to track:
117/// - Which children are present and in what order
118/// - Which children have been moved within the list
119/// - Which positions are "locked" (cannot be freely reordered)
120/// - Hangon nodes at each position
121#[derive(Debug, Clone)]
122pub struct MergeList {
123    /// The entries in the list.
124    entries: Vec<MergeEntry>,
125    /// Index from base match (node id) to entry position.
126    index: HashMap<u64, usize>,
127    /// The parent branch node.
128    entry_parent: Option<NodeRef>,
129    /// Current tail position (last valid entry).
130    tail_pos: i32,
131}
132
133impl MergeList {
134    /// Creates a new empty merge list.
135    pub fn new(parent: Option<NodeRef>) -> Self {
136        MergeList {
137            entries: Vec::new(),
138            index: HashMap::new(),
139            entry_parent: parent,
140            tail_pos: -1,
141        }
142    }
143
144    /// Creates a merge list from a branch node's children.
145    pub fn from_branch(parent: &NodeRef, edit_log: &mut EditLog) -> Self {
146        let mut ml = MergeList::new(Some(parent.clone()));
147
148        let base_match = BranchNode::base_match(parent);
149
150        if base_match.is_none() {
151            // Parent is unmatched - treat all children as inserts/copies
152            ml.add_entry(MergeEntry::start());
153            let child_count = parent.borrow().child_count();
154            for i in 0..child_count {
155                if let Some(child) = parent.borrow().child(i).cloned() {
156                    ml.add_hangon(child);
157                }
158            }
159            ml.lock_neighborhood(0, 1);
160            ml.add_entry(MergeEntry::end());
161            return ml;
162        }
163
164        let base_parent = base_match.unwrap();
165        let mut base_matches: HashMap<u64, Option<usize>> = HashMap::new();
166        let mut prev_child_pos: i32 = -1;
167
168        ml.add_entry(MergeEntry::start());
169
170        let child_count = parent.borrow().child_count();
171        for i in 0..child_count {
172            let current = match parent.borrow().child(i).cloned() {
173                Some(c) => c,
174                None => continue,
175            };
176
177            let current_base_match = BranchNode::base_match(&current);
178
179            if let Some(base_node) = current_base_match {
180                // Has a base match - check if it's from the expected parent
181                let base_parent_match = {
182                    let borrowed = base_node.borrow();
183                    borrowed
184                        .parent()
185                        .upgrade()
186                        .map(|p| p.borrow().id())
187                        .unwrap_or(0)
188                };
189                let expected_parent_id = base_parent.borrow().id();
190
191                if base_parent_match != expected_parent_id {
192                    // Copied from elsewhere
193                    ml.add_hangon(current);
194                    ml.lock_neighborhood(0, 1);
195                } else {
196                    let base_id = base_node.borrow().id();
197
198                    if base_matches.contains_key(&base_id) {
199                        // N-th copy (n > 1)
200                        ml.add_hangon(current);
201                        if let Some(first_pos) = base_matches.get(&base_id).and_then(|p| *p) {
202                            // Lock the first occurrence as well
203                            ml.lock_neighborhood_at(first_pos, 1, 1);
204                            // Put None to avoid locking more than once
205                            base_matches.insert(base_id, None);
206                        }
207                        ml.lock_neighborhood(0, 1);
208                    } else {
209                        // Regular node from base
210                        ml.add_node(current.clone());
211                        base_matches.insert(base_id, Some(ml.tail_pos as usize));
212
213                        let child_pos = base_node.borrow().child_pos();
214                        let child_pos_i32 = if child_pos < 0 { -2 } else { child_pos };
215
216                        if (prev_child_pos + 1) != child_pos_i32 {
217                            // Out of sequence - check if actually moved
218                            let mut moved = false;
219
220                            if prev_child_pos != -2
221                                && child_pos_i32 != -2
222                                && prev_child_pos < child_pos_i32
223                            {
224                                // Check if nodes between prev and current are deleted
225                                for j in 0..child_count {
226                                    if let Some(sibling) = parent.borrow().child(j).cloned() {
227                                        if let Some(sibling_base) = BranchNode::base_match(&sibling)
228                                        {
229                                            let base_pos = sibling_base.borrow().child_pos();
230                                            if base_pos >= 0
231                                                && base_pos > prev_child_pos
232                                                && base_pos < child_pos_i32
233                                            {
234                                                moved = true;
235                                                break;
236                                            }
237                                        }
238                                    }
239                                }
240                            } else {
241                                moved = true;
242                            }
243
244                            if moved {
245                                ml.lock_neighborhood(1, 0);
246                                ml.set_moved(true);
247                            } else {
248                                ml.set_moved(false);
249                            }
250                        }
251                        prev_child_pos = child_pos_i32;
252                    }
253                }
254            } else {
255                // Insert node - no base match
256                ml.add_hangon(current);
257                ml.lock_neighborhood(0, 1);
258            }
259        }
260
261        ml.add_entry(MergeEntry::end());
262
263        // Check for end out-of-sequence
264        let base_child_count = base_parent.borrow().child_count() as i32;
265        if (prev_child_pos + 1) != base_child_count {
266            ml.lock_neighborhood(1, 0);
267        }
268
269        // Suppress unused warning - edit_log is used in full implementation
270        let _ = edit_log;
271
272        ml
273    }
274
275    /// Adds an entry to the list.
276    pub fn add_entry(&mut self, mut entry: MergeEntry) {
277        self.tail_pos += 1;
278        let pos = self.tail_pos as usize;
279
280        // Ensure capacity
281        while self.entries.len() <= pos {
282            self.entries.push(MergeEntry::new(None));
283        }
284
285        // Preserve locked status from placeholder
286        if let Some(existing) = self.entries.get(pos) {
287            entry.locked = entry.locked || existing.locked;
288        }
289
290        // Index by base match
291        if let Some(base) = entry.base_match() {
292            let base_id = base.borrow().id();
293            self.index.insert(base_id, pos);
294        }
295
296        self.entries[pos] = entry;
297    }
298
299    /// Adds a node as an entry.
300    pub fn add_node(&mut self, node: NodeRef) {
301        self.add_entry(MergeEntry::new(Some(node)));
302    }
303
304    /// Adds a hangon to the current entry.
305    pub fn add_hangon(&mut self, node: NodeRef) {
306        if self.tail_pos >= 0 {
307            let pos = self.tail_pos as usize;
308            if pos < self.entries.len() {
309                self.entries[pos].add_hangon(node);
310            }
311        }
312    }
313
314    /// Sets the moved flag on the current entry.
315    pub fn set_moved(&mut self, moved: bool) {
316        if self.tail_pos >= 0 {
317            let pos = self.tail_pos as usize;
318            if pos < self.entries.len() {
319                self.entries[pos].moved = moved;
320            }
321        }
322    }
323
324    /// Locks entries around the current position.
325    pub fn lock_neighborhood(&mut self, left: usize, right: usize) {
326        self.lock_neighborhood_at(self.tail_pos as usize, left, right);
327    }
328
329    /// Locks entries around the given position.
330    pub fn lock_neighborhood_at(&mut self, pos: usize, left: usize, right: usize) {
331        let end_pos = pos + right + 1;
332
333        // Ensure capacity with locked placeholders
334        while self.entries.len() < end_pos {
335            self.entries.push(MergeEntry::new(None));
336        }
337
338        let start = pos.saturating_sub(left);
339        let end = std::cmp::min(pos + right + 1, self.entries.len());
340
341        for i in start..end {
342            self.entries[i].locked = true;
343        }
344    }
345
346    /// Returns the number of entries (including START and END).
347    pub fn entry_count(&self) -> usize {
348        (self.tail_pos + 1) as usize
349    }
350
351    /// Returns an entry by index.
352    pub fn entry(&self, ix: usize) -> Option<&MergeEntry> {
353        self.entries.get(ix)
354    }
355
356    /// Returns a mutable entry by index.
357    pub fn entry_mut(&mut self, ix: usize) -> Option<&mut MergeEntry> {
358        self.entries.get_mut(ix)
359    }
360
361    /// Returns all entries.
362    pub fn entries(&self) -> &[MergeEntry] {
363        &self.entries[..self.entry_count()]
364    }
365
366    /// Removes the entry at the given index.
367    pub fn remove_entry_at(&mut self, ix: usize) {
368        if ix < self.entries.len() {
369            self.entries.remove(ix);
370            self.tail_pos -= 1;
371
372            // Rebuild index
373            self.index.clear();
374            for (i, entry) in self.entries.iter().enumerate() {
375                if let Some(base) = entry.base_match() {
376                    self.index.insert(base.borrow().id(), i);
377                }
378            }
379        }
380    }
381
382    /// Moves hangons from entry at ix to its predecessor.
383    pub fn move_hangons_to_predecessor(&mut self, ix: usize) {
384        if ix > 0 && ix < self.entries.len() {
385            let hangons: Vec<NodeRef> = self.entries[ix].hangons.drain(..).collect();
386            for hangon in hangons {
387                self.entries[ix - 1].add_hangon(hangon);
388            }
389        }
390    }
391
392    /// Finds the index of the partner entry (by base match).
393    pub fn find_partner(&self, entry: &MergeEntry) -> Option<&MergeEntry> {
394        let ix = self.find_partner_index(Some(entry))?;
395        self.entries.get(ix)
396    }
397
398    /// Finds the index of the partner entry.
399    pub fn find_partner_index(&self, entry: Option<&MergeEntry>) -> Option<usize> {
400        let entry = entry?;
401
402        if entry.is_start {
403            return Some(0);
404        }
405        if entry.is_end {
406            return Some(self.entry_count().saturating_sub(1));
407        }
408
409        let base = entry.base_match()?;
410        let base_id = base.borrow().id();
411        self.index.get(&base_id).copied()
412    }
413
414    /// Finds the index of a base node in the list.
415    pub fn match_in_list(&self, base: &NodeRef) -> Option<usize> {
416        let base_id = base.borrow().id();
417        self.index.get(&base_id).copied()
418    }
419
420    /// Returns the entry parent.
421    pub fn entry_parent(&self) -> Option<NodeRef> {
422        self.entry_parent.clone()
423    }
424
425    /// Checks if the given index is the END marker.
426    pub fn is_end(&self, ix: usize) -> bool {
427        self.entries.get(ix).is_none_or(|e| e.is_end)
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use super::*;
434    use crate::node::{new_branch_node, XmlContent, XmlElement};
435
436    fn make_element(name: &str) -> XmlContent {
437        XmlContent::Element(XmlElement::new(
438            name.to_string(),
439            std::collections::HashMap::new(),
440        ))
441    }
442
443    #[test]
444    fn test_merge_entry_basic() {
445        let entry = MergeEntry::new(None);
446        assert!(entry.node().is_none());
447        assert!(!entry.is_start);
448        assert!(!entry.is_end);
449        assert!(!entry.locked);
450        assert!(!entry.moved);
451        assert_eq!(entry.hangon_count(), 0);
452    }
453
454    #[test]
455    fn test_merge_entry_start_end() {
456        let start = MergeEntry::start();
457        assert!(start.is_start);
458        assert!(!start.is_end);
459
460        let end = MergeEntry::end();
461        assert!(!end.is_start);
462        assert!(end.is_end);
463    }
464
465    #[test]
466    fn test_merge_entry_hangons() {
467        let mut entry = MergeEntry::new(None);
468        let node = new_branch_node(Some(make_element("child")));
469
470        entry.add_hangon(node.clone());
471        assert_eq!(entry.hangon_count(), 1);
472        assert_eq!(entry.hangons()[0].borrow().id(), node.borrow().id());
473    }
474
475    #[test]
476    fn test_merge_list_empty() {
477        let ml = MergeList::new(None);
478        assert_eq!(ml.entry_count(), 0);
479        assert!(ml.entry_parent().is_none());
480    }
481
482    #[test]
483    fn test_merge_list_add_entries() {
484        let mut ml = MergeList::new(None);
485
486        ml.add_entry(MergeEntry::start());
487        assert_eq!(ml.entry_count(), 1);
488
489        ml.add_node(new_branch_node(Some(make_element("a"))));
490        assert_eq!(ml.entry_count(), 2);
491
492        ml.add_entry(MergeEntry::end());
493        assert_eq!(ml.entry_count(), 3);
494
495        assert!(ml.entry(0).unwrap().is_start);
496        assert!(ml.entry(2).unwrap().is_end);
497    }
498
499    #[test]
500    fn test_merge_list_locking() {
501        let mut ml = MergeList::new(None);
502
503        ml.add_entry(MergeEntry::start());
504        ml.add_node(new_branch_node(Some(make_element("a"))));
505        ml.add_node(new_branch_node(Some(make_element("b"))));
506        ml.add_entry(MergeEntry::end());
507
508        ml.lock_neighborhood_at(1, 1, 1);
509
510        assert!(ml.entry(0).unwrap().locked);
511        assert!(ml.entry(1).unwrap().locked);
512        assert!(ml.entry(2).unwrap().locked);
513        assert!(!ml.entry(3).unwrap().locked);
514    }
515
516    #[test]
517    fn test_merge_list_remove_entry() {
518        let mut ml = MergeList::new(None);
519
520        ml.add_entry(MergeEntry::start());
521        ml.add_node(new_branch_node(Some(make_element("a"))));
522        ml.add_node(new_branch_node(Some(make_element("b"))));
523        ml.add_entry(MergeEntry::end());
524
525        assert_eq!(ml.entry_count(), 4);
526
527        ml.remove_entry_at(2);
528        assert_eq!(ml.entry_count(), 3);
529    }
530}