use crate::extraction::tree_sitter_helpers::{get_child_by_field, get_node_text};
use crate::extraction::tree_sitter_types::{ImportInfo, LanguageExtractor, Visibility};
use crate::web_tree_sitter::SyntaxNode;
pub struct JavaExtractor;
pub const JAVA_EXTRACTOR: JavaExtractor = JavaExtractor;
pub fn java_extractor() -> &'static JavaExtractor {
&JAVA_EXTRACTOR
}
const JAVA_NON_CLASS_RETURN_NODES: &[&str] = &[
"void_type",
"integral_type",
"floating_point_type",
"boolean_type",
];
fn extract_java_return_type(node: &SyntaxNode, source: &str) -> Option<String> {
let type_node = get_child_by_field(node, "type")?;
if JAVA_NON_CLASS_RETURN_NODES.contains(&type_node.type_name().as_str())
|| type_node.type_name() == "array_type"
{
return None;
}
let raw = strip_angle_generics(get_node_text(type_node, source).trim());
let last = raw.split('.').next_back()?.trim();
is_identifier(last).then(|| last.to_string())
}
fn strip_angle_generics(input: &str) -> String {
let mut out = String::new();
let mut depth = 0usize;
for ch in input.chars() {
match ch {
'<' => depth += 1,
'>' => depth = depth.saturating_sub(1),
_ if depth == 0 => out.push(ch),
_ => {}
}
}
out
}
fn is_identifier(value: &str) -> bool {
let mut chars = value.chars();
matches!(chars.next(), Some('_') | Some('A'..='Z') | Some('a'..='z'))
&& chars.all(|c| c == '_' || c.is_ascii_alphanumeric())
}
impl LanguageExtractor for JavaExtractor {
fn function_types(&self) -> &'static [&'static str] {
&[]
}
fn class_types(&self) -> &'static [&'static str] {
&["class_declaration"]
}
fn method_types(&self) -> &'static [&'static str] {
&["method_declaration", "constructor_declaration"]
}
fn interface_types(&self) -> &'static [&'static str] {
&["interface_declaration", "annotation_type_declaration"]
}
fn struct_types(&self) -> &'static [&'static str] {
&[]
}
fn enum_types(&self) -> &'static [&'static str] {
&["enum_declaration"]
}
fn enum_member_types(&self) -> &'static [&'static str] {
&["enum_constant"]
}
fn type_alias_types(&self) -> &'static [&'static str] {
&[]
}
fn import_types(&self) -> &'static [&'static str] {
&["import_declaration"]
}
fn call_types(&self) -> &'static [&'static str] {
&["method_invocation"]
}
fn variable_types(&self) -> &'static [&'static str] {
&["local_variable_declaration"]
}
fn field_types(&self) -> &'static [&'static str] {
&["field_declaration"]
}
fn name_field(&self) -> &'static str {
"name"
}
fn body_field(&self) -> &'static str {
"body"
}
fn params_field(&self) -> &'static str {
"parameters"
}
fn return_field(&self) -> Option<&'static str> {
Some("type")
}
fn get_return_type(&self, node: &SyntaxNode, source: &str) -> Option<String> {
extract_java_return_type(node, source)
}
fn get_signature(&self, node: &SyntaxNode, source: &str) -> Option<String> {
let params = get_child_by_field(node, "parameters")?;
let params_text = get_node_text(params, source);
let sig = get_child_by_field(node, "type")
.map(|return_type| format!("{} {}", get_node_text(return_type, source), params_text))
.unwrap_or(params_text);
Some(sig)
}
fn get_visibility(&self, node: &SyntaxNode) -> Option<Visibility> {
for i in 0..node.child_count() {
let Some(child) = node.child(i) else {
continue;
};
if child.type_name() == "modifiers" {
let text = child.text();
if text.contains("public") {
return Some(Visibility::Public);
}
if text.contains("private") {
return Some(Visibility::Private);
}
if text.contains("protected") {
return Some(Visibility::Protected);
}
}
}
None
}
fn is_static(&self, node: &SyntaxNode) -> bool {
(0..node.child_count()).any(|i| {
node.child(i)
.is_some_and(|c| c.type_name() == "modifiers" && c.text().contains("static"))
})
}
fn is_const(&self, node: &SyntaxNode) -> bool {
for i in 0..node.child_count() {
let Some(child) = node.child(i) else {
continue;
};
if child.type_name() == "modifiers" {
let text = child.text();
return text.contains("static") && text.contains("final");
}
}
false
}
fn extract_import(&self, node: &SyntaxNode, source: &str) -> Option<ImportInfo> {
let scoped_id = node
.named_children()
.into_iter()
.find(|c| c.type_name() == "scoped_identifier")?;
Some(ImportInfo {
module_name: get_node_text(&scoped_id, source),
signature: get_node_text(node, source).trim().to_string(),
handled_refs: false,
})
}
fn package_types(&self) -> &'static [&'static str] {
&["package_declaration"]
}
fn extract_package(&self, node: &SyntaxNode, source: &str) -> Option<String> {
node.named_children()
.into_iter()
.find(|c| c.type_name() == "scoped_identifier" || c.type_name() == "identifier")
.map(|id| get_node_text(&id, source).trim().to_string())
}
}