use crate::core::error::{Error, Result};
use std::collections::HashMap;
use std::str;
use std::sync::{Arc, RwLock};
#[derive(Debug, Clone, Copy)]
pub struct StringMetadata {
pub offset: u32,
pub length: u32,
pub hash: u64,
}
#[derive(Debug)]
struct PoolInner {
buffer: Vec<u8>,
strings: Vec<StringMetadata>,
dedup_index: HashMap<u64, Vec<u32>>,
total_additions: usize,
}
impl PoolInner {
fn new(capacity: usize) -> Self {
Self {
buffer: Vec::with_capacity(capacity),
strings: Vec::new(),
dedup_index: HashMap::new(),
total_additions: 0,
}
}
fn bytes_of(&self, metadata: &StringMetadata) -> Option<&[u8]> {
let start = metadata.offset as usize;
let end = start.checked_add(metadata.length as usize)?;
self.buffer.get(start..end)
}
}
#[derive(Debug)]
pub struct SimpleUnifiedStringPool {
inner: Arc<RwLock<PoolInner>>,
}
#[derive(Debug, Clone)]
pub struct SimpleStringView {
metadata: StringMetadata,
pool: Arc<RwLock<PoolInner>>,
}
fn read_lock(inner: &Arc<RwLock<PoolInner>>) -> Result<std::sync::RwLockReadGuard<'_, PoolInner>> {
inner
.read()
.map_err(|_| Error::InvalidOperation("String pool lock is poisoned".to_string()))
}
fn write_lock(
inner: &Arc<RwLock<PoolInner>>,
) -> Result<std::sync::RwLockWriteGuard<'_, PoolInner>> {
inner
.write()
.map_err(|_| Error::InvalidOperation("String pool lock is poisoned".to_string()))
}
impl SimpleStringView {
pub fn as_str(&self) -> Result<String> {
self.with_str_ref(|s| s.to_string())
}
pub fn as_bytes(&self) -> Result<Vec<u8>> {
let guard = read_lock(&self.pool)?;
guard
.bytes_of(&self.metadata)
.map(|b| b.to_vec())
.ok_or_else(|| Error::InvalidOperation("String extends beyond buffer".to_string()))
}
pub fn len(&self) -> usize {
self.metadata.length as usize
}
pub fn is_empty(&self) -> bool {
self.metadata.length == 0
}
pub fn metadata(&self) -> StringMetadata {
self.metadata
}
pub fn substring(&self, start: usize, end: usize) -> Result<SimpleStringView> {
if start > end || end > self.len() {
return Err(Error::InvalidOperation(format!(
"Invalid substring range {}..{} for a {} byte string",
start,
end,
self.len()
)));
}
let boundaries_ok =
self.with_str_ref(|s| s.is_char_boundary(start) && s.is_char_boundary(end))?;
if !boundaries_ok {
return Err(Error::InvalidOperation(format!(
"Substring range {}..{} does not fall on UTF-8 character boundaries",
start, end
)));
}
let offset = u32::try_from(self.metadata.offset as usize + start).map_err(|_| {
Error::InvalidOperation("Substring offset exceeds the 32-bit pool address space".into())
})?;
let length = u32::try_from(end - start).map_err(|_| {
Error::InvalidOperation("Substring length exceeds the 32-bit pool limit".into())
})?;
Ok(SimpleStringView {
metadata: StringMetadata {
offset,
length,
hash: 0,
},
pool: Arc::clone(&self.pool),
})
}
pub fn with_str_ref<F, R>(&self, f: F) -> Result<R>
where
F: FnOnce(&str) -> R,
{
let guard = read_lock(&self.pool)?;
let data = guard
.bytes_of(&self.metadata)
.ok_or_else(|| Error::InvalidOperation("String extends beyond buffer".to_string()))?;
let s = str::from_utf8(data)
.map_err(|e| Error::InvalidOperation(format!("Invalid UTF-8: {}", e)))?;
Ok(f(s))
}
}
impl std::fmt::Display for SimpleStringView {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self.as_str() {
Ok(s) => write!(f, "{}", s),
Err(_) => write!(f, "<invalid UTF-8>"),
}
}
}
impl SimpleUnifiedStringPool {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(PoolInner::new(1024 * 1024))),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
inner: Arc::new(RwLock::new(PoolInner::new(capacity))),
}
}
pub fn add_string(&self, s: &str) -> Result<u32> {
let bytes = s.as_bytes();
let hash = hash_string(s);
let mut inner = write_lock(&self.inner)?;
inner.total_additions += 1;
let candidates = inner.dedup_index.get(&hash).cloned().unwrap_or_default();
for id in candidates {
if let Some(metadata) = inner.strings.get(id as usize).copied() {
if metadata.length as usize == bytes.len()
&& inner.bytes_of(&metadata) == Some(bytes)
{
return Ok(id);
}
}
}
let offset = u32::try_from(inner.buffer.len()).map_err(|_| {
Error::InvalidOperation(
"String pool exceeded its 32-bit address space (4 GiB of string data)".to_string(),
)
})?;
let length = u32::try_from(bytes.len()).map_err(|_| {
Error::InvalidOperation("Individual strings are limited to 4 GiB".to_string())
})?;
u32::try_from(offset as u64 + length as u64).map_err(|_| {
Error::InvalidOperation(
"String pool exceeded its 32-bit address space (4 GiB of string data)".to_string(),
)
})?;
let id = u32::try_from(inner.strings.len()).map_err(|_| {
Error::InvalidOperation("String pool exceeded 2^32 distinct strings".to_string())
})?;
inner.buffer.extend_from_slice(bytes);
inner.strings.push(StringMetadata {
offset,
length,
hash,
});
inner.dedup_index.entry(hash).or_default().push(id);
Ok(id)
}
pub fn add_strings(&self, strings: &[String]) -> Result<Vec<u32>> {
let mut result = Vec::with_capacity(strings.len());
for s in strings {
result.push(self.add_string(s)?);
}
Ok(result)
}
pub fn get_string(&self, string_id: u32) -> Result<SimpleStringView> {
let metadata = {
let inner = read_lock(&self.inner)?;
inner
.strings
.get(string_id as usize)
.copied()
.ok_or_else(|| {
Error::InvalidOperation(format!("String ID {} not found", string_id))
})?
};
Ok(SimpleStringView {
metadata,
pool: Arc::clone(&self.inner),
})
}
pub fn get_strings(&self, string_ids: &[u32]) -> Result<Vec<SimpleStringView>> {
let mut result = Vec::with_capacity(string_ids.len());
for &id in string_ids {
result.push(self.get_string(id)?);
}
Ok(result)
}
pub fn len(&self) -> Result<usize> {
Ok(read_lock(&self.inner)?.strings.len())
}
pub fn is_empty(&self) -> Result<bool> {
Ok(read_lock(&self.inner)?.strings.is_empty())
}
pub fn stats(&self) -> Result<SimpleStringPoolStats> {
let inner = read_lock(&self.inner)?;
let total_additions = inner.total_additions;
let unique_strings = inner.strings.len();
Ok(SimpleStringPoolStats {
total_strings: total_additions,
unique_strings,
total_bytes: inner.buffer.len(),
buffer_capacity: inner.buffer.capacity(),
deduplication_ratio: if total_additions > 0 {
1.0 - (unique_strings as f64 / total_additions as f64)
} else {
0.0
},
memory_efficiency: if inner.buffer.capacity() > 0 {
inner.buffer.len() as f64 / inner.buffer.capacity() as f64
} else {
0.0
},
})
}
}
fn hash_string(s: &str) -> u64 {
use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
let mut hasher = DefaultHasher::new();
s.hash(&mut hasher);
hasher.finish()
}
impl Default for SimpleUnifiedStringPool {
fn default() -> Self {
Self::new()
}
}
impl Clone for SimpleUnifiedStringPool {
fn clone(&self) -> Self {
Self {
inner: Arc::clone(&self.inner),
}
}
}
#[derive(Debug, Clone)]
pub struct SimpleStringPoolStats {
pub total_strings: usize,
pub unique_strings: usize,
pub total_bytes: usize,
pub buffer_capacity: usize,
pub deduplication_ratio: f64,
pub memory_efficiency: f64,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_string_pool_creation() {
let pool = SimpleUnifiedStringPool::new();
let stats = pool.stats().expect("operation should succeed");
assert_eq!(stats.total_strings, 0);
assert_eq!(stats.unique_strings, 0);
assert!(stats.buffer_capacity > 0);
}
#[test]
fn test_string_addition_and_retrieval() {
let pool = SimpleUnifiedStringPool::new();
let id1 = pool.add_string("hello").expect("operation should succeed");
let id2 = pool.add_string("world").expect("operation should succeed");
let id3 = pool.add_string("hello").expect("operation should succeed");
assert_ne!(id1, id2);
assert_eq!(id1, id3);
let view1 = pool.get_string(id1).expect("operation should succeed");
let view2 = pool.get_string(id2).expect("operation should succeed");
assert_eq!(view1.as_str().expect("operation should succeed"), "hello");
assert_eq!(view2.as_str().expect("operation should succeed"), "world");
let stats = pool.stats().expect("operation should succeed");
assert_eq!(stats.total_strings, 3);
assert_eq!(stats.unique_strings, 2);
}
#[test]
fn test_multiple_string_operations() {
let pool = SimpleUnifiedStringPool::new();
let strings = vec![
"apple".to_string(),
"banana".to_string(),
"cherry".to_string(),
"apple".to_string(),
];
let ids = pool
.add_strings(&strings)
.expect("operation should succeed");
assert_eq!(ids.len(), 4);
assert_eq!(ids[0], ids[3]);
let views = pool.get_strings(&ids).expect("operation should succeed");
assert_eq!(views.len(), 4);
for (view, expected) in views.iter().zip(strings.iter()) {
assert_eq!(&view.as_str().expect("operation should succeed"), expected);
}
}
#[test]
fn test_zero_copy_access() {
let pool = SimpleUnifiedStringPool::new();
let id = pool
.add_string("hello world")
.expect("operation should succeed");
let view = pool.get_string(id).expect("operation should succeed");
let result = view
.with_str_ref(|s| s.to_uppercase())
.expect("operation should succeed");
assert_eq!(result, "HELLO WORLD");
let starts_with_hello = view
.with_str_ref(|s| s.starts_with("hello"))
.expect("operation should succeed");
assert!(starts_with_hello);
}
#[test]
fn test_substring() {
let pool = SimpleUnifiedStringPool::new();
let id = pool
.add_string("hello world")
.expect("operation should succeed");
let view = pool.get_string(id).expect("operation should succeed");
let substring = view.substring(0, 5).expect("operation should succeed");
assert_eq!(
substring.as_str().expect("operation should succeed"),
"hello"
);
let substring2 = view.substring(6, 11).expect("operation should succeed");
assert_eq!(
substring2.as_str().expect("operation should succeed"),
"world"
);
}
#[test]
fn substring_rejects_non_character_boundaries() {
let pool = SimpleUnifiedStringPool::new();
let id = pool.add_string("日本語").expect("add");
let view = pool.get_string(id).expect("get");
assert!(view.substring(0, 1).is_err());
assert!(view.substring(1, 3).is_err());
assert_eq!(
view.substring(0, 3).expect("valid").as_str().expect("str"),
"日"
);
assert!(view.substring(0, 99).is_err());
}
#[test]
fn deduplication_is_content_verified() {
let pool = SimpleUnifiedStringPool::new();
let mut ids = Vec::new();
for i in 0..512 {
ids.push(pool.add_string(&format!("value-{}", i)).expect("add"));
}
for (i, id) in ids.iter().enumerate() {
let view = pool.get_string(*id).expect("get");
assert_eq!(
view.as_str().expect("str"),
format!("value-{}", i),
"id {} returned the wrong string",
id
);
}
let stats = pool.stats().expect("stats");
assert_eq!(stats.unique_strings, 512);
}
#[test]
fn test_pool_statistics() {
let pool = SimpleUnifiedStringPool::new();
pool.add_string("test").expect("operation should succeed");
pool.add_string("data").expect("operation should succeed");
pool.add_string("test").expect("operation should succeed");
let stats = pool.stats().expect("operation should succeed");
assert_eq!(stats.total_strings, 3);
assert_eq!(stats.unique_strings, 2);
assert!(stats.total_bytes > 0);
assert!(stats.deduplication_ratio > 0.0);
}
#[test]
fn concurrent_adds_do_not_corrupt_the_buffer() {
use std::thread;
let pool = SimpleUnifiedStringPool::new();
let mut handles = Vec::new();
for t in 0..8 {
let pool = pool.clone();
handles.push(thread::spawn(move || {
let mut local = Vec::new();
for i in 0..200 {
let value = format!("thread{}-value{}", t, i);
let id = pool.add_string(&value).expect("add");
local.push((id, value));
}
local
}));
}
let mut all = Vec::new();
for handle in handles {
all.extend(handle.join().expect("thread panicked"));
}
for (id, expected) in all {
let view = pool.get_string(id).expect("get");
assert_eq!(view.as_str().expect("str"), expected);
}
}
#[test]
fn clones_share_state() {
let pool = SimpleUnifiedStringPool::new();
let clone = pool.clone();
let id = clone.add_string("shared").expect("add");
assert_eq!(
pool.get_string(id).expect("get").as_str().expect("str"),
"shared"
);
assert_eq!(pool.len().expect("len"), 1);
}
}