use java_lang::{Result, parse_file};
use std::path::{Path, PathBuf};
use walkdir::WalkDir;
fn find_android_sdk_sources() -> Option<PathBuf> {
if let Ok(android_home) = std::env::var("ANDROID_HOME") {
let sources_dir = PathBuf::from(&android_home).join("sources");
if sources_dir.exists() {
if let Ok(entries) = std::fs::read_dir(&sources_dir) {
let mut highest_api = 0.0;
let mut best_match = None;
for entry in entries.flatten() {
if let Ok(meta) = entry.metadata()
&& meta.is_dir()
&& let Some(name) = entry.file_name().to_str()
&& let Some(api_str) = name.strip_prefix("android-")
&& let Ok(api) = api_str.parse::<f64>()
&& api > highest_api
{
highest_api = api;
best_match = Some(entry.path());
}
}
if let Some(path) = best_match {
return Some(path);
}
}
}
}
let common_paths = [
r"C:\Users\%USERNAME%\AppData\Local\Android\Sdk\sources\android-35",
r"C:\Android\Sdk\sources\android-35",
r"C:\Program Files\Android\Sdk\sources\android-35",
r"D:\Android\Sdk\sources\android-35",
r"~/Library/Android/sdk/sources/android-35", r"~/Android/Sdk/sources/android-35", ];
for path in &common_paths {
let expanded = if path.contains("%USERNAME%") {
path.replace("%USERNAME%", &std::env::var("USERNAME").unwrap_or_default())
} else {
path.to_string()
};
let path_buf = PathBuf::from(&expanded);
if path_buf.exists() {
return Some(path_buf);
}
}
None
}
fn count_java_files(dir: &Path) -> usize {
WalkDir::new(dir)
.into_iter()
.filter_map(|e| e.ok())
.filter(|e| {
e.file_type().is_file()
&& e.path()
.extension()
.map(|ext| ext == "java")
.unwrap_or(false)
})
.count()
}
fn parse_java_file(path: &Path) -> Result<()> {
let _ = parse_file::<java_lang::ast::CompilationUnit>(path)?;
Ok(())
}
#[test]
fn test_android_sdk_sources() {
let sdk_sources = match find_android_sdk_sources() {
Some(path) => path,
None => {
println!("Skipping Android SDK test - SDK not found.");
println!("Set ANDROID_HOME environment variable to your Android SDK installation.");
return;
}
};
println!("Found Android SDK sources at: {}", sdk_sources.display());
let total_files = count_java_files(&sdk_sources);
println!("Found {} Java files to parse", total_files);
if total_files == 0 {
panic!("No Java files found in Android SDK sources directory");
}
let mut success_count = 0;
let mut fail_count = 0;
let mut errors: Vec<(PathBuf, String)> = Vec::new();
for entry in WalkDir::new(&sdk_sources) {
let entry = match entry {
Ok(e) => e,
Err(e) => {
eprintln!("Error reading directory entry: {}", e);
continue;
}
};
if !entry.file_type().is_file() {
continue;
}
let path = entry.path();
if path.extension().map(|ext| ext != "java").unwrap_or(true) {
continue;
}
let rel_path = path.strip_prefix(&sdk_sources).unwrap_or(path);
match parse_java_file(path) {
Ok(_) => {
success_count += 1;
if success_count % 100 == 0 {
println!(
"Progress: {}/{} files parsed successfully",
success_count, total_files
);
}
}
Err(e) => {
fail_count += 1;
let error_msg = format!("{}", e);
eprintln!("FAILED: {} - {}", rel_path.display(), error_msg);
if let Ok(content) = std::fs::read_to_string(path)
&& let Some(offset_str) = error_msg.split("byte offset ").nth(1)
&& let Some(offset_end) = offset_str.find(' ')
&& let Ok(offset) = offset_str[..offset_end].parse::<usize>()
{
let start = offset.saturating_sub(200);
let end = (offset + 200).min(content.len());
eprintln!(" Context around error:");
eprintln!(" {}", &content[start..end].replace('\n', "\\n"));
}
errors.push((rel_path.to_path_buf(), error_msg));
if fail_count >= 100 {
eprintln!("Too many failures, stopping...");
break;
}
}
}
}
println!("\n=== Parsing Summary ===");
println!("Total files: {}", total_files);
println!("Successful: {}", success_count);
println!("Failed: {}", fail_count);
if !errors.is_empty() {
println!("\n=== Failed Files ===");
for (path, error) in &errors {
println!(" {}: {}", path.display(), error);
}
panic!(
"{} out of {} Java files failed to parse",
fail_count, total_files
);
}
assert_eq!(
success_count, total_files,
"All Java files should parse successfully"
);
}
#[test]
fn test_android_sdk_sample() {
let sdk_sources = match find_android_sdk_sources() {
Some(path) => path,
None => {
println!("Skipping Android SDK sample test - SDK not found.");
return;
}
};
let test_packages = ["java/lang", "android/os", "android/util", "java/util"];
for package in &test_packages {
let package_path = sdk_sources.join(package);
if package_path.exists() {
println!("Testing package: {}", package);
let mut success = 0;
let mut failed = 0;
for entry in WalkDir::new(&package_path).max_depth(1) {
let entry = match entry {
Ok(e) => e,
Err(_) => continue,
};
if !entry.file_type().is_file() {
continue;
}
let path = entry.path();
if path.extension().map(|ext| ext != "java").unwrap_or(true) {
continue;
}
match parse_java_file(path) {
Ok(_) => success += 1,
Err(e) => {
failed += 1;
let rel_path = path.strip_prefix(&sdk_sources).unwrap_or(path);
eprintln!(" FAILED: {} - {}", rel_path.display(), e);
}
}
}
println!(" Parsed: {}/{} files", success, success + failed);
if failed > 0 {
panic!("Package {} has {} parse failures", package, failed);
}
return;
}
}
println!("No standard packages found to test");
}
#[test]
#[ignore = "Manual test for debugging specific files"]
fn test_parse_single_android_file() {
let test_file = PathBuf::from(r"path\to\specific\File.java");
if !test_file.exists() {
println!("Test file not found: {}", test_file.display());
return;
}
println!("Parsing: {}", test_file.display());
match parse_java_file(&test_file) {
Ok(_) => println!("Success!"),
Err(e) => {
println!("Error: {}", e);
panic!("Parse failed");
}
}
}