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}