Skip to main content

java_diff_utils_rs/patch/
chunk.rs

1use serde::{Deserialize, Serialize};
2use std::fmt;
3
4use super::error::PatchError;
5use super::verify_chunk::VerifyChunk;
6
7/// Represents a contiguous sub-sequence (chunk) of items participating in a diff/patch operation.
8#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)]
9#[serde(bound(serialize = "T: Serialize", deserialize = "T: Deserialize<'de>"))]
10pub struct Chunk<T> {
11    position: usize,
12    lines: Vec<T>,
13    change_position: Option<Vec<usize>>,
14}
15
16impl<T> Chunk<T> {
17    /// Creates a new `Chunk` with a starting position, lines, and optional positions of modified lines.
18    pub fn new(position: usize, lines: Vec<T>, change_position: Option<Vec<usize>>) -> Self {
19        Self {
20            position,
21            lines,
22            change_position,
23        }
24    }
25
26    /// Convenience constructor creating a `Chunk` without granular line-change tracking indices.
27    pub fn with_lines(position: usize, lines: Vec<T>) -> Self {
28        Self::new(position, lines, None)
29    }
30
31    /// Verifies that this chunk's saved lines match the corresponding region in the target slice.
32    pub fn verify_chunk(&self, target: &[T]) -> Result<VerifyChunk, PatchError>
33    where
34        T: PartialEq,
35    {
36        self.verify_chunk_at(target, 0, self.position)
37    }
38
39    /// Verifies that this chunk matches the target sequence starting at `position`, taking `fuzz` context into account.
40    pub fn verify_chunk_at(
41        &self,
42        target: &[T],
43        fuzz: usize,
44        position: usize,
45    ) -> Result<VerifyChunk, PatchError>
46    where
47        T: PartialEq,
48    {
49        let start_index = fuzz;
50        let last_index = self.len().saturating_sub(fuzz);
51        let last = position + self.len().saturating_sub(1);
52
53        if position.saturating_add(fuzz) > target.len() || last.saturating_sub(fuzz) > target.len()
54        {
55            return Ok(VerifyChunk::PositionOutOfTarget);
56        }
57
58        for i in start_index..last_index {
59            let target_idx = position + i;
60            if target_idx >= target.len() || target[target_idx] != self.lines[i] {
61                return Ok(VerifyChunk::ContentDoesNotMatchTarget);
62            }
63        }
64
65        Ok(VerifyChunk::Ok)
66    }
67    /// Returns the zero-based start position of this chunk.
68    #[inline]
69    pub fn position(&self) -> usize {
70        self.position
71    }
72
73    /// Returns a slice reference to the chunk's lines.
74    #[inline]
75    pub fn lines(&self) -> &[T] {
76        &self.lines
77    }
78
79    /// Returns a mutable slice reference to the chunk's lines.
80    #[inline]
81    pub fn lines_mut(&mut self) -> &mut Vec<T> {
82        &mut self.lines
83    }
84
85    /// Sets or replaces the lines stored inside this chunk.
86    pub fn set_lines(&mut self, lines: Vec<T>) {
87        self.lines = lines;
88    }
89
90    /// Returns an optional slice reference to the change position indices, if present.
91    #[inline]
92    pub fn change_position(&self) -> Option<&[usize]> {
93        self.change_position.as_deref()
94    }
95
96    /// Returns the number of lines contained in this chunk.
97    #[inline]
98    pub fn len(&self) -> usize {
99        self.lines.len()
100    }
101
102    /// Alias method for `len` matching common Java/C# diff library APIS.
103    #[inline]
104    pub fn size(&self) -> usize {
105        self.lines.len()
106    }
107
108    /// Returns `true` if this chunk contains no lines.
109    #[inline]
110    pub fn is_empty(&self) -> bool {
111        self.lines.is_empty()
112    }
113
114    /// Returns the zero-based index of the last line in the chunk (if non-empty).
115    #[inline]
116    pub fn last(&self) -> usize {
117        if self.lines.is_empty() {
118            self.position
119        } else {
120            self.position + self.lines.len() - 1
121        }
122    }
123}
124
125impl<T: fmt::Debug> fmt::Display for Chunk<T> {
126    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
127        write!(
128            f,
129            "[position: {}, size: {}, lines: {:?}]",
130            self.position,
131            self.len(),
132            self.lines
133        )
134    }
135}