use core::{
num::NonZeroU64,
sync::atomic::{AtomicU64, Ordering},
};
use std::{collections::HashMap, sync::Mutex};
use crate::{
runner::aligner::{
aligner::Aligner,
key::{AlignerKey, AlignmentFallback},
},
types::Lang,
};
pub enum AlignmentLookup<'a> {
Hit {
matched: AlignerKey,
aligner: &'a Mutex<Aligner>,
},
AnyFallback {
aligner: &'a Mutex<Aligner>,
},
Miss {
fallback: AlignmentFallback,
},
}
pub struct AlignmentSet {
aligners: HashMap<AlignerKey, Mutex<Aligner>>,
fallback: AlignmentFallback,
id: NonZeroU64,
}
impl AlignmentSet {
pub(super) fn from_parts(
aligners: HashMap<AlignerKey, Mutex<Aligner>>,
fallback: AlignmentFallback,
) -> Self {
static COUNTER: AtomicU64 = AtomicU64::new(1);
let raw = COUNTER.fetch_add(1, Ordering::Relaxed);
Self {
aligners,
fallback,
id: NonZeroU64::new(raw).expect("AlignmentSet counter overflowed u64"),
}
}
pub(crate) const fn id(&self) -> NonZeroU64 {
self.id
}
pub const fn fallback(&self) -> AlignmentFallback {
self.fallback
}
pub fn len(&self) -> usize {
self.aligners.len()
}
pub fn is_empty(&self) -> bool {
self.aligners.is_empty()
}
pub fn detect_oov(
&self,
job: &crate::AlignWorkItem,
) -> Result<crate::JobDetection, crate::types::WorkFailure> {
use crate::core::{AlignmentUnit, OovDetection, OovEvent, OovKind};
let units: Vec<(AlignmentUnit, &str, &Lang)> = if job.runs().is_empty() {
vec![(AlignmentUnit::Whole, job.text().as_str(), job.language())]
} else {
job
.runs()
.iter()
.enumerate()
.map(|(index, run)| (AlignmentUnit::Run(index), run.text(), run.language()))
.collect()
};
let mut detections = Vec::with_capacity(units.len());
for (unit, text, language) in units {
let detection = match self.lookup(language) {
AlignmentLookup::Hit { aligner, .. } | AlignmentLookup::AnyFallback { aligner } => {
let guard = aligner.lock().unwrap_or_else(|p| p.into_inner());
let mut events = guard.detect_events(text)?;
for event in &mut events {
event.set_language(language.clone());
}
OovDetection::of_unit(unit, language.clone(), events, Some(guard.id()))
}
AlignmentLookup::Miss { .. } => OovDetection::of_unit(
unit,
language.clone(),
vec![OovEvent::new(OovKind::NotInspected, 0, 0, language.clone())],
None,
),
};
detections.push(detection);
}
Ok(crate::JobDetection::new(job, self.id, detections))
}
pub fn lookup<'a>(&'a self, language: &Lang) -> AlignmentLookup<'a> {
let lang_key = AlignerKey::Lang(language.clone());
if let Some(m) = self.aligners.get(&lang_key) {
return AlignmentLookup::Hit {
matched: lang_key,
aligner: m,
};
}
if let Some(m) = self.aligners.get(&AlignerKey::Any) {
return AlignmentLookup::AnyFallback { aligner: m };
}
AlignmentLookup::Miss {
fallback: self.fallback,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::runner::aligner::{normalizer::DynTextNormalizer, normalizers::EnglishNormalizer};
#[test]
fn empty_set_misses_with_default_fallback() {
let set = AlignmentSet::from_parts(HashMap::new(), AlignmentFallback::SkipChunk);
match set.lookup(&Lang::En) {
AlignmentLookup::Miss { fallback } => {
assert_eq!(fallback, AlignmentFallback::SkipChunk);
}
_ => panic!("expected Miss"),
}
}
#[test]
fn empty_set_misses_with_error_fallback() {
let set = AlignmentSet::from_parts(HashMap::new(), AlignmentFallback::Error);
match set.lookup(&Lang::Zh) {
AlignmentLookup::Miss { fallback } => {
assert_eq!(fallback, AlignmentFallback::Error);
}
_ => panic!("expected Miss"),
}
}
#[test]
fn is_empty_reports_correctly() {
let set = AlignmentSet::from_parts(HashMap::new(), AlignmentFallback::SkipChunk);
assert!(set.is_empty());
assert_eq!(set.len(), 0);
}
#[test]
fn normalizer_imports_compile() {
let _: DynTextNormalizer = Box::new(EnglishNormalizer::new());
}
}