use std::io::Write;
use crate::error::{Error, Result};
use crate::index::lucene::codec::data_output::{CodecOutput, header_length};
const CODEC_NAME_DATA: &str = "Lucene40StoredFieldsData";
const CODEC_NAME_INDEX: &str = "Lucene40StoredFieldsIndex";
const VERSION_CURRENT: i32 = 0;
const FIELD_IS_BINARY: u8 = 1 << 1;
const NUMERIC_BIT_SHIFT: u8 = 3;
#[derive(Clone, Copy, Debug)]
pub enum StoredValue<'value> {
Text(&'value str),
Binary(&'value [u8]),
Integer(i32),
Long(i64),
Float(f32),
Double(f64),
}
impl StoredValue<'_> {
const fn bits(self) -> u8 {
match self {
Self::Text(_) => 0,
Self::Binary(_) => FIELD_IS_BINARY,
Self::Integer(_) => 1 << NUMERIC_BIT_SHIFT,
Self::Long(_) => 2 << NUMERIC_BIT_SHIFT,
Self::Float(_) => 3 << NUMERIC_BIT_SHIFT,
Self::Double(_) => 4 << NUMERIC_BIT_SHIFT,
}
}
}
#[derive(Debug)]
pub struct StoredFieldsFiles<Sink> {
pub data: Sink,
pub index: Sink,
}
pub struct StoredFieldsWriter<Sink: Write> {
data: CodecOutput<Sink>,
index: CodecOutput<Sink>,
open_document: Option<usize>,
written_fields: usize,
documents: u64,
}
impl<Sink: Write> StoredFieldsWriter<Sink> {
pub fn new(data: Sink, index: Sink) -> Result<Self> {
let mut data = CodecOutput::new(data);
let mut index = CodecOutput::new(index);
data.write_header(CODEC_NAME_DATA, VERSION_CURRENT)?;
index.write_header(CODEC_NAME_INDEX, VERSION_CURRENT)?;
Ok(Self {
data,
index,
open_document: None,
written_fields: 0,
documents: 0,
})
}
pub fn start_document(&mut self, field_count: usize) -> Result<()> {
if self.open_document.is_some() {
return Err(Error::InvalidFormat {
details: "a document is already open; finish it before starting another".to_owned(),
});
}
self.index.write_long(self.data.position() as i64)?;
self.data.write_vint(
i32::try_from(field_count).map_err(|_| Error::InvalidFormat {
details: format!("{field_count} stored fields does not fit a vint"),
})?,
)?;
self.open_document = Some(field_count);
self.written_fields = 0;
Ok(())
}
pub fn write_field(&mut self, number: i32, value: StoredValue<'_>) -> Result<()> {
let Some(field_count) = self.open_document else {
return Err(Error::InvalidFormat {
details: "a stored field outside any document".to_owned(),
});
};
if self.written_fields == field_count {
return Err(Error::InvalidFormat {
details: format!(
"the document said it held {field_count} stored fields and this is one \
more; the count is written before the fields and cannot be revised"
),
});
}
self.data.write_vint(number)?;
self.data.write_byte(value.bits())?;
match value {
StoredValue::Text(text) => self.data.write_string(text)?,
StoredValue::Binary(bytes) => {
self.data
.write_vint(i32::try_from(bytes.len()).map_err(|_| {
Error::InvalidFormat {
details: format!("{} bytes does not fit a vint", bytes.len()),
}
})?)?;
self.data.write_bytes(bytes)?;
}
StoredValue::Integer(value) => self.data.write_int(value)?,
StoredValue::Long(value) => self.data.write_long(value)?,
StoredValue::Float(value) => self.data.write_int(value.to_bits() as i32)?,
StoredValue::Double(value) => self.data.write_long(value.to_bits() as i64)?,
}
self.written_fields += 1;
Ok(())
}
pub fn finish_document(&mut self) -> Result<()> {
let Some(field_count) = self.open_document.take() else {
return Err(Error::InvalidFormat {
details: "no document is open".to_owned(),
});
};
if self.written_fields != field_count {
return Err(Error::InvalidFormat {
details: format!(
"the document said it held {field_count} stored fields and wrote {}; \
the count is written before them and cannot be revised",
self.written_fields
),
});
}
self.documents += 1;
Ok(())
}
pub fn finish(self, document_count: u64) -> Result<StoredFieldsFiles<Sink>> {
if self.open_document.is_some() {
return Err(Error::InvalidFormat {
details: "a document is still open".to_owned(),
});
}
let expected = header_length(CODEC_NAME_INDEX) as u64 + document_count * 8;
if expected != self.index.position() {
return Err(Error::InvalidFormat {
details: format!(
"fdx size mismatch: {document_count} documents make {expected} bytes and \
the file is {}",
self.index.position()
),
});
}
if document_count != self.documents {
return Err(Error::InvalidFormat {
details: format!(
"{} documents were written and {document_count} were declared",
self.documents
),
});
}
Ok(StoredFieldsFiles {
data: self.data.into_inner(),
index: self.index.into_inner(),
})
}
}