Skip to main content

java_diff_utils_rs/patch/
change_delta.rs

1use std::fmt;
2
3use super::chunk::Chunk;
4use super::delta::Delta;
5use super::delta_type::DeltaType;
6use super::error::PatchError;
7
8/// Describes a change-delta representing replaced content between original and revised sequences.
9#[derive(Debug, Clone, PartialEq, Eq, Hash)]
10pub struct ChangeDelta<T> {
11    inner: Delta<T>,
12}
13
14impl<T> ChangeDelta<T> {
15    /// Creates a new `ChangeDelta` with the given source and target chunks.
16    pub fn new(source: Chunk<T>, target: Chunk<T>) -> Self {
17        Self {
18            inner: Delta::new(DeltaType::Change, source, target),
19        }
20    }
21
22    /// Returns a reference to the underlying inner [`Delta`].
23    #[inline]
24    pub fn delta(&self) -> &Delta<T> {
25        &self.inner
26    }
27
28    /// Consumes `self` and returns the inner [`Delta`].
29    #[inline]
30    pub fn into_delta(self) -> Delta<T> {
31        self.inner
32    }
33
34    /// Returns a reference to the source chunk.
35    #[inline]
36    pub fn source(&self) -> &Chunk<T> {
37        self.inner.source()
38    }
39
40    /// Returns a reference to the target chunk.
41    #[inline]
42    pub fn target(&self) -> &Chunk<T> {
43        self.inner.target()
44    }
45
46    /// Applies this change delta to the target vector.
47    ///
48    /// Replaces the element range at the source chunk's position with the target chunk's lines.
49    pub fn apply_to(&self, target: &mut Vec<T>) -> Result<(), PatchError>
50    where
51        T: Clone + PartialEq,
52    {
53        let position = self.source().position();
54        let source_size = self.source().len();
55
56        if position > target.len() || position + source_size > target.len() {
57            return Err(PatchError::PatchFailed(format!(
58                "ChangeDelta position {} (size {}) out of bounds for target length {}",
59                position,
60                source_size,
61                target.len()
62            )));
63        }
64
65        // Efficient bulk splice replacement instead of item-by-item removal and insertion
66        target.splice(
67            position..position + source_size,
68            self.target().lines().iter().cloned(),
69        );
70
71        Ok(())
72    }
73
74    /// Restores (un-applies) this change delta on the target sequence.
75    ///
76    /// Replaces the element range at the target chunk's position with the original source lines.
77    pub fn restore(&self, target: &mut Vec<T>) -> Result<(), PatchError>
78    where
79        T: Clone + PartialEq,
80    {
81        let position = self.target().position();
82        let target_size = self.target().len();
83
84        if position > target.len() || position + target_size > target.len() {
85            return Err(PatchError::PatchFailed(format!(
86                "ChangeDelta restore position {} (size {}) out of bounds for target length {}",
87                position,
88                target_size,
89                target.len()
90            )));
91        }
92
93        target.splice(
94            position..position + target_size,
95            self.source().lines().iter().cloned(),
96        );
97
98        Ok(())
99    }
100
101    /// Applies the patch with a fuzzy tolerance context offset.
102    pub fn apply_fuzzy_to_at(
103        &self,
104        target: &mut Vec<T>,
105        _fuzz: usize,
106        position: usize,
107    ) -> Result<(), PatchError>
108    where
109        T: Clone + PartialEq,
110    {
111        if position > target.len() {
112            return Err(PatchError::PatchFailed(format!(
113                "Fuzzy patch position {} out of bounds for target length {}",
114                position,
115                target.len()
116            )));
117        }
118
119        let end = (position + self.source().len()).min(target.len());
120
121        target.splice(position..end, self.target().lines().iter().cloned());
122
123        Ok(())
124    }
125
126    /// Creates a new `ChangeDelta` with custom source and target chunks.
127    #[must_use]
128    pub fn with_chunks(&self, original: Chunk<T>, revised: Chunk<T>) -> Self {
129        Self::new(original, revised)
130    }
131}
132
133impl<T: fmt::Debug> fmt::Display for ChangeDelta<T> {
134    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
135        write!(
136            f,
137            "[ChangeDelta, position: {}, lines: {:?} to {:?}]",
138            self.source().position(),
139            self.source().lines(),
140            self.target().lines()
141        )
142    }
143}
144
145impl<T> From<ChangeDelta<T>> for Delta<T> {
146    fn from(change: ChangeDelta<T>) -> Self {
147        change.inner
148    }
149}