use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::{BufRead, BufReader};
use std::path::{Path, PathBuf};
use rayon::prelude::*;
use super::super::predicates::java_declared_types;
pub(super) struct JavaClassIndex {
pub(super) local_classes: HashSet<String>,
pub(super) class_files: HashMap<String, Vec<String>>,
}
pub(super) fn build_java_class_index(
root_path: &Path,
candidate_files: &[PathBuf],
) -> JavaClassIndex {
let (local_classes, mut class_files) = candidate_files
.par_iter()
.map(|path| {
let mut local_classes = HashSet::new();
let mut class_files: HashMap<String, Vec<String>> = HashMap::new();
let ext = path
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or_default();
if ext != "java" {
return (local_classes, class_files);
}
let Ok(file) = File::open(path) else {
return (local_classes, class_files);
};
let rel = path.strip_prefix(root_path).unwrap_or(path);
let rel_str = rel.to_string_lossy().replace('\\', "/");
let mut package = None;
for line in BufReader::new(file).lines().map_while(Result::ok) {
let line = line.trim();
if package.is_none() {
package = line
.strip_prefix("package ")
.map(|rest| rest.trim().trim_end_matches(';').trim().to_string());
}
for class_name in java_declared_types(line) {
local_classes.insert(class_name.clone());
if let Some(package) = package.as_deref()
&& !package.is_empty()
{
let fqcn = format!("{package}.{class_name}");
local_classes.insert(fqcn.clone());
class_files.entry(fqcn).or_default().push(rel_str.clone());
}
}
}
(local_classes, class_files)
})
.reduce(
|| (HashSet::new(), HashMap::new()),
|mut acc, (classes, files)| {
acc.0.extend(classes);
for (fqcn, paths) in files {
acc.1.entry(fqcn).or_default().extend(paths);
}
acc
},
);
for files in class_files.values_mut() {
files.sort();
files.dedup();
}
JavaClassIndex {
local_classes,
class_files,
}
}
pub(super) fn build_kotlin_package_files(
root_path: &Path,
candidate_files: &[PathBuf],
) -> HashMap<String, Vec<String>> {
let mut package_files = candidate_files
.par_iter()
.filter_map(|path| {
let ext = path
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or_default();
if ext != "kt" && ext != "kts" {
return None;
}
let file = File::open(path).ok()?;
let rel = path.strip_prefix(root_path).unwrap_or(path);
let rel_str = rel.to_string_lossy().replace('\\', "/");
let mut package = String::new();
for line in BufReader::new(file).lines().map_while(Result::ok) {
let line = line.trim();
if line.is_empty()
|| line.starts_with("//")
|| line.starts_with("/*")
|| line.starts_with('*')
|| line.starts_with('@')
{
continue;
}
if let Some(rest) = line.strip_prefix("package ") {
package = rest.trim().trim_end_matches(';').trim().to_string();
}
break;
}
Some((package, rel_str))
})
.fold(
HashMap::<String, Vec<String>>::new,
|mut acc, (package, rel)| {
acc.entry(package).or_default().push(rel);
acc
},
)
.reduce(HashMap::<String, Vec<String>>::new, |mut acc, map| {
for (package, files) in map {
acc.entry(package).or_default().extend(files);
}
acc
});
for files in package_files.values_mut() {
files.sort();
files.dedup();
}
package_files
}
pub(super) fn build_scala_package_files(
root_path: &Path,
candidate_files: &[PathBuf],
) -> HashMap<String, Vec<String>> {
let mut package_files = candidate_files
.par_iter()
.filter_map(|path| {
let ext = path
.extension()
.and_then(|ext| ext.to_str())
.unwrap_or_default();
if ext != "scala" && ext != "sc" {
return None;
}
let file = File::open(path).ok()?;
let rel = path.strip_prefix(root_path).unwrap_or(path);
let rel_str = rel.to_string_lossy().replace('\\', "/");
let mut package_segments = Vec::new();
for line in BufReader::new(file).lines().map_while(Result::ok) {
let line = line.trim();
if line.is_empty()
|| line.starts_with("//")
|| line.starts_with("/*")
|| line.starts_with('*')
{
continue;
}
let Some(rest) = line.strip_prefix("package ") else {
break;
};
let rest = rest.trim();
if rest.starts_with("object ") {
break;
}
let segment = rest
.trim_end_matches([';', '{'])
.split_whitespace()
.next()
.unwrap_or_default()
.trim_end_matches('{')
.trim();
if segment.is_empty() {
break;
}
package_segments.push(segment.to_string());
}
Some((package_segments.join("."), rel_str))
})
.fold(
HashMap::<String, Vec<String>>::new,
|mut acc, (package, rel)| {
acc.entry(package).or_default().push(rel);
acc
},
)
.reduce(HashMap::<String, Vec<String>>::new, |mut acc, map| {
for (package, files) in map {
acc.entry(package).or_default().extend(files);
}
acc
});
for files in package_files.values_mut() {
files.sort();
files.dedup();
}
package_files
}