Skip to main content

lib_bcsv_jmap/
jmap.rs

1use indexmap::IndexMap;
2
3use crate::entry::Entry;
4use crate::error::{JMapError, Result};
5use crate::field::{Field, FieldType, FieldValue};
6use crate::hash::HashTable;
7
8/// The main JMap that holds fields and entries. So basically the in-memory representation of a BCSV file
9///
10/// This is a table-like structure where each field represents a column
11/// and each entry represents a row of data
12///
13/// Basically implemented what i see on [this page](https://www.lumasworkshop.com/wiki/BCSV_(File_format))
14#[derive(Debug)]
15pub struct JMapInfo<H: HashTable> {
16    /// The hash table used for field name lookups
17    hash_table: H,
18    /// Fields indexed by their hash
19    fields: IndexMap<u32, Field>,
20    /// List of entries
21    entries: Vec<Entry>,
22    /// Size of a single entry in bytes
23    pub(crate) entry_size: u32,
24}
25
26impl<H: HashTable> JMapInfo<H> {
27    /// Create a new empty JMapInfo with the given hash table
28    ///
29    /// # Arguments
30    /// - `hash_table` - The hash table to use for field name lookups
31    pub fn new(hash_table: H) -> Self {
32        Self {
33            hash_table,
34            fields: IndexMap::new(),
35            entries: Vec::new(),
36            entry_size: 0,
37        }
38    }
39
40    /// Get a reference to the hash table
41    pub fn hash_table(&self) -> &H {
42        &self.hash_table
43    }
44
45    /// Get a mutable reference to the hash table
46    pub fn hash_table_mut(&mut self) -> &mut H {
47        &mut self.hash_table
48    }
49
50    /// Get the number of fields (columns)
51    pub fn num_fields(&self) -> usize {
52        self.fields.len()
53    }
54
55    /// Get the number of entries (rows)
56    pub fn len(&self) -> usize {
57        self.entries.len()
58    }
59
60    /// Check if entries are empty
61    pub fn is_empty(&self) -> bool {
62        self.entries.is_empty()
63    }
64
65    /// Get an iterator over all fields
66    pub fn fields(&self) -> impl Iterator<Item = &Field> {
67        self.fields.values()
68    }
69
70    /// Get an iterator over all field hashes
71    pub fn field_hashes(&self) -> impl Iterator<Item = &u32> {
72        self.fields.keys()
73    }
74
75    /// Get a field by hash
76    pub fn get_field_by_hash(&self, hash: u32) -> Option<&Field> {
77        self.fields.get(&hash)
78    }
79
80    /// Get a field by name
81    pub fn get_field(&self, name: &str) -> Option<&Field> {
82        let hash = self.hash_table.calc(name);
83        self.fields.get(&hash)
84    }
85
86    /// Check if a field exists by hash
87    pub fn contains_field_hash(&self, hash: u32) -> bool {
88        self.fields.contains_key(&hash)
89    }
90
91    /// Check if a field exists by name
92    pub fn contains_field(&self, name: &str) -> bool {
93        let hash = self.hash_table.calc(name);
94        self.fields.contains_key(&hash)
95    }
96
97    /// Get the name of a field by its hash
98    pub fn field_name(&self, hash: u32) -> String {
99        self.hash_table.find(hash)
100    }
101
102    /// Create a new field with the given name and type
103    ///
104    /// # Arguments
105    /// - `name` - The name of the field to create
106    /// - `field_type` - The type of the field to create
107    /// - `default` - The default value for the field to create
108    ///
109    /// # Errors
110    /// - `JMapError::TypeMismatch` if the default value is not compatible with the field type
111    /// - `JMapError::FieldAlreadyExists` if a field with the same name already exists
112    ///
113    /// # Returns
114    /// Ok(()) if the field was created successfully, or an error if the field could not be created
115    pub fn create_field(
116        &mut self,
117        name: &str,
118        field_type: FieldType,
119        default: FieldValue,
120    ) -> Result<()> {
121        if !default.is_compatible_with(field_type) {
122            return Err(JMapError::TypeMismatch {
123                expected: field_type.csv_name(),
124                got: default.type_name(),
125            });
126        }
127
128        let hash = self.hash_table.add(name);
129
130        if self.fields.contains_key(&hash) {
131            return Err(JMapError::FieldAlreadyExists(name.to_string()));
132        }
133
134        let field = Field::with_default(hash, field_type, default.clone());
135        self.fields.insert(hash, field);
136
137        // Add default value to all existing entries
138        for entry in &mut self.entries {
139            entry.set_by_hash(hash, default.clone());
140        }
141
142        Ok(())
143    }
144
145    /// Remove a field from the container
146    ///
147    /// # Arguments
148    /// - `name` - The name of the field to remove
149    ///
150    /// # Errors
151    /// - `JMapError::FieldNotFound` if the field does not exist
152    ///
153    /// # Returns
154    /// Ok(()) if the field was removed successfully, or an error if the field could not be found
155    pub fn drop_field(&mut self, name: &str) -> Result<()> {
156        let hash = self.hash_table.calc(name);
157
158        if !self.fields.contains_key(&hash) {
159            return Err(JMapError::FieldNotFound(name.to_string()));
160        }
161
162        self.fields.swap_remove(&hash);
163
164        for entry in &mut self.entries {
165            entry.data_mut().remove(&hash);
166        }
167
168        Ok(())
169    }
170
171    /// Get a slice of all entries
172    pub fn entries(&self) -> &[Entry] {
173        &self.entries
174    }
175
176    /// Get a mutable slice of all entries
177    pub fn entries_mut(&mut self) -> &mut [Entry] {
178        &mut self.entries
179    }
180
181    /// Get an entry by index
182    pub fn get_entry(&self, index: usize) -> Option<&Entry> {
183        self.entries.get(index)
184    }
185
186    /// Get a mutable entry by index
187    pub fn get_entry_mut(&mut self, index: usize) -> Option<&mut Entry> {
188        self.entries.get_mut(index)
189    }
190
191    /// Create a new entry with default values for all fields
192    pub fn create_entry(&mut self) -> &mut Entry {
193        let mut entry = Entry::with_capacity(self.fields.len());
194
195        for field in self.fields.values() {
196            entry.set_by_hash(field.hash, field.default.clone());
197        }
198
199        self.entries.push(entry);
200        self.entries.last_mut().unwrap()
201    }
202
203    /// Remove an entry by index
204    ///
205    /// # Arguments
206    /// - `index` - The index of the entry to remove
207    ///
208    /// # Errors
209    /// - `JMapError::EntryIndexOutOfBounds` if the index is out of bounds
210    ///
211    /// # Returns
212    /// Ok(Entry) if the entry was removed successfully, or an error if the index was out of bounds
213    pub fn remove_entry(&mut self, index: usize) -> Result<Entry> {
214        if index >= self.entries.len() {
215            return Err(JMapError::EntryIndexOutOfBounds {
216                index,
217                len: self.entries.len(),
218            });
219        }
220
221        Ok(self.entries.remove(index))
222    }
223
224    /// Clear all entries but keep the field definitions
225    pub fn clear_entries(&mut self) {
226        self.entries.clear();
227    }
228
229    /// Sort entries by a custom key function
230    ///
231    /// # Arguments
232    /// - `f` - The key function to sort by
233    ///
234    /// # Types
235    /// - `F` - The type of the key function, which must be a function that takes a reference to an `Entry` and returns a key of type `K`
236    /// - `K` - The type of the key returned by the key function, which must implement the `Ord` trait for sorting
237    pub fn sort_entries_by<F, K>(&mut self, f: F)
238    where
239        F: FnMut(&Entry) -> K,
240        K: Ord,
241    {
242        self.entries.sort_by_key(f);
243    }
244
245    /// Iterate over entries
246    pub fn iter(&self) -> impl Iterator<Item = &Entry> {
247        self.entries.iter()
248    }
249
250    /// Iterate over entries mutably
251    pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut Entry> {
252        self.entries.iter_mut()
253    }
254
255    /// Get internal access to fields (for I/O operations)
256    pub(crate) fn fields_map(&self) -> &IndexMap<u32, Field> {
257        &self.fields
258    }
259
260    /// Get mutable internal access to fields (for I/O operations)
261    pub(crate) fn fields_map_mut(&mut self) -> &mut IndexMap<u32, Field> {
262        &mut self.fields
263    }
264
265    /// Get mutable access to entries (for I/O operations)
266    pub(crate) fn entries_vec_mut(&mut self) -> &mut Vec<Entry> {
267        &mut self.entries
268    }
269}
270
271/// Implement IntoIterator for JMapInfo to allow iterating over entries directly
272/// This allows using `for entry in jmap` syntax to iterate over entries, as well as iterating over references and mutable references to JMapInfo
273/// The item type is `Entry` for owned iteration, `&Entry` for reference iteration, and `&mut Entry` for mutable reference iteration
274impl<H: HashTable> IntoIterator for JMapInfo<H> {
275    type Item = Entry;
276    type IntoIter = std::vec::IntoIter<Entry>;
277
278    fn into_iter(self) -> Self::IntoIter {
279        self.entries.into_iter()
280    }
281}
282
283/// Implement IntoIterator for references to JMapInfo to allow iterating over entries by reference
284/// # Types
285/// - `H` - The type of the hash table used by the JMapInfo, which must implement the `HashTable` trait
286///
287/// # Lifetime
288/// - `'a` - The lifetime of the reference to the JMapInfo
289impl<'a, H: HashTable> IntoIterator for &'a JMapInfo<H> {
290    type Item = &'a Entry;
291    type IntoIter = std::slice::Iter<'a, Entry>;
292
293    fn into_iter(self) -> Self::IntoIter {
294        self.entries.iter()
295    }
296}
297
298impl<'a, H: HashTable> IntoIterator for &'a mut JMapInfo<H> {
299    type Item = &'a mut Entry;
300    type IntoIter = std::slice::IterMut<'a, Entry>;
301
302    fn into_iter(self) -> Self::IntoIter {
303        self.entries.iter_mut()
304    }
305}