use std::path::Path;
use anyhow::Result;
use tree_sitter::{Language, Node};
use super::{Entity, FileInsight, ImportStmt, KindRule, LanguageProcessor, SharedProcessor};
pub struct JavaProcessor;
impl JavaProcessor {
pub fn new() -> Result<Self> {
Ok(Self)
}
}
const KINDS: &[KindRule] = &[
KindRule::with_sig("class_declaration", "class", '{'),
KindRule::with_sig("interface_declaration", "interface", '{'),
KindRule::with_sig("enum_declaration", "enum", '{'),
KindRule::with_sig("record_declaration", "class", '{'),
KindRule::with_sig("method_declaration", "function", '{'),
KindRule::with_sig("constructor_declaration", "function", '{'),
];
impl SharedProcessor for JavaProcessor {
fn language() -> &'static str { "Java" }
fn grammar() -> Language { tree_sitter_java::LANGUAGE.into() }
fn kinds() -> &'static [KindRule] {
KINDS
}
fn handle_special(node: Node, bytes: &[u8], entities: &mut Vec<Entity>, imports: &mut Vec<ImportStmt>) {
match node.kind() {
"field_declaration" => {
let mut cur = node.walk();
if cur.goto_first_child() {
loop {
if cur.node().kind() == "variable_declarator"
&& let Some(name) = cur.node().child_by_field_name("name")
.and_then(|n| n.utf8_text(bytes).ok())
{
entities.push(Entity {
name: name.to_string(), kind: "variable".to_string(),
line_start: node.start_position().row + 1,
line_end: node.end_position().row + 1,
doc_comment: None, signature: None, visibility: None,
});
}
if !cur.goto_next_sibling() { break; }
}
}
}
"import_declaration" => {
if let Ok(text) = node.utf8_text(bytes) {
let name = text
.strip_prefix("import ")
.and_then(|s| s.strip_suffix(';'))
.map(|s| s.trim())
.unwrap_or(text.trim());
imports.push(ImportStmt {
source: name.to_string(),
alias: None,
line: node.start_position().row + 1,
});
}
}
_ => {}
}
}
fn fallback(source: &str) -> (Vec<Entity>, Vec<ImportStmt>) {
let mut entities = Vec::new();
let mut imports = Vec::new();
for (i, line) in source.lines().enumerate() {
let line_no = i + 1;
let t = line.trim();
if let Some(rest) = t.strip_prefix("import ") {
let path = rest.trim_end_matches(';').trim();
imports.push(ImportStmt { source: path.to_string(), alias: None, line: line_no });
continue;
}
for prefix in &["class ", "interface ", "enum ", "record "] {
if let Some(rest) = t.find(prefix).map(|pos| &t[pos..]) {
let name = rest.strip_prefix(prefix)
.and_then(|s| s.split(&['{', ' ', '<', ';', '(', '}'][..]).next())
.map(|s| s.trim());
if let Some(name) = name
&& !name.is_empty()
{
let kind = match *prefix {
"class " | "record " => "class",
"interface " => "interface",
"enum " => "enum",
_ => "class",
};
entities.push(Entity {
name: name.to_string(), kind: kind.to_string(),
line_start: line_no, line_end: line_no,
doc_comment: None, signature: Some(t.to_string()), visibility: None,
});
break;
}
}
}
}
(entities, imports)
}
}
impl LanguageProcessor for JavaProcessor {
fn name(&self) -> &'static str { Self::language() }
fn extensions(&self) -> &[&str] { &[".java"] }
fn parse(&self, source: &str, path: &Path) -> Result<FileInsight> {
Self::parse_file(source, path)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_java_basics() {
let source = r#"import java.util.List;
import java.io.File;
class Calculator {
public int add(int a, int b) { return a + b; }
private int count;
}
interface Runnable {
void run();
}
"#;
let proc = JavaProcessor::new().unwrap();
let result = proc.parse(source, Path::new("Test.java")).unwrap();
assert!(result.entities.iter().any(|e| e.name == "Calculator" && e.kind == "class"));
assert!(result.entities.iter().any(|e| e.name == "Runnable" && e.kind == "interface"));
assert!(result.entities.iter().any(|e| e.name == "add" && e.kind == "function"));
assert!(result.entities.iter().any(|e| e.name == "count" && e.kind == "variable"));
assert!(result.imports.iter().any(|i| i.source == "java.util.List"));
assert!(result.imports.iter().any(|i| i.source == "java.io.File"));
}
#[test]
fn test_parse_java_constructor() {
let source = r#"public class Calculator {
public Calculator() { this(0); }
public Calculator(int seed) { this.seed = seed; }
private int seed;
}
"#;
let proc = JavaProcessor::new().unwrap();
let result = proc.parse(source, Path::new("Test.java")).unwrap();
let ctors: Vec<&str> = result.entities.iter()
.filter(|e| e.name == "Calculator" && e.kind == "function")
.map(|e| e.name.as_str())
.collect();
assert!(!ctors.is_empty(), "构造器应解析为 function: {:?}", result.entities);
}
}