use crate::cfg_builder;
use crate::cfg_loop_handler;
use crate::parse::traits::calculate_complexity;
use crate::parse::traits::clean_call_text;
use crate::parse::traits::{Block, Edge, EdgeType, Parameter, Visibility};
use crate::parse::traits::{
CodeIntelligence, ComplexityMetrics, Error, Graph, ImportInfo, Result, SignatureInfo,
};
use tree_sitter::Parser;
pub struct JavaParser;
impl Default for JavaParser {
fn default() -> Self {
Self::new()
}
}
impl JavaParser {
pub fn new() -> Self {
Self
}
fn extract_all_definitions(
&self,
source: &[u8],
root: tree_sitter::Node<'_>,
) -> Vec<SignatureInfo> {
let mut signatures = Vec::new();
Self::visit_java_node(&root, source, &mut signatures, &[]);
signatures
}
fn visit_java_node(
node: &tree_sitter::Node<'_>,
source: &[u8],
signatures: &mut Vec<SignatureInfo>,
parent_path: &[String],
) {
match node.kind() {
"method_declaration" => {
if let Some(signature) = extract_method_signature(node, source, parent_path) {
signatures.push(signature);
}
}
"constructor_declaration" => {
if let Some(signature) = extract_constructor_signature(node, source, parent_path) {
signatures.push(signature);
}
}
"class_declaration" => {
Self::push_java_type_signature(node, source, parent_path, "class", signatures);
let child_path = Self::java_type_child_path(node, source, parent_path);
Self::visit_java_children(node, source, signatures, &child_path);
}
"interface_declaration" => {
Self::push_java_type_signature(node, source, parent_path, "interface", signatures);
let child_path = Self::java_type_child_path(node, source, parent_path);
Self::visit_java_children(node, source, signatures, &child_path);
}
"enum_declaration" => {
Self::push_java_type_signature(node, source, parent_path, "enum", signatures);
let child_path = Self::java_type_child_path(node, source, parent_path);
Self::visit_java_children(node, source, signatures, &child_path);
}
"field_declaration" => Self::extract_java_fields(node, source, signatures, parent_path),
_ => Self::visit_java_children(node, source, signatures, parent_path),
}
}
fn java_type_child_path(
node: &tree_sitter::Node<'_>,
source: &[u8],
parent_path: &[String],
) -> Vec<String> {
let mut path = parent_path.to_vec();
if let Some(name) = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
{
path.push(name.trim().to_string());
}
path
}
fn visit_java_children(
node: &tree_sitter::Node<'_>,
source: &[u8],
signatures: &mut Vec<SignatureInfo>,
parent_path: &[String],
) {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
Self::visit_java_node(&child, source, signatures, parent_path);
}
}
fn push_java_type_signature(
node: &tree_sitter::Node<'_>,
source: &[u8],
parent_path: &[String],
type_name: &str,
signatures: &mut Vec<SignatureInfo>,
) {
let Some(name) = node
.child_by_field_name("name")
.and_then(|name| name.utf8_text(source).ok())
else {
return;
};
let qualified_name = if parent_path.is_empty() {
name.to_string()
} else {
format!("{}.{}", parent_path.join("."), name)
};
signatures.push(SignatureInfo {
name: name.to_string(),
qualified_name,
parameters: vec![],
return_type: Some(type_name.to_string()),
visibility: extract_visibility(node, source),
is_async: false,
is_method: false,
docstring: extract_docstring(node, source),
calls: vec![],
imports: vec![],
byte_range: (node.start_byte(), node.end_byte()),
flow_facts: vec![],
cyclomatic_complexity: 0,
});
}
fn extract_java_fields(
node: &tree_sitter::Node<'_>,
source: &[u8],
signatures: &mut Vec<SignatureInfo>,
parent_path: &[String],
) {
let type_annotation = node
.child_by_field_name("type")
.and_then(|type_node| type_node.utf8_text(source).ok())
.map(|text| text.trim().to_string());
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() != "variable_declarator" {
continue;
}
let Some(name) = child
.child_by_field_name("name")
.and_then(|name| name.utf8_text(source).ok())
else {
continue;
};
signatures.push(SignatureInfo {
name: name.to_string(),
qualified_name: if parent_path.is_empty() {
name.to_string()
} else {
format!("{}.{}", parent_path.join("."), name)
},
parameters: vec![],
return_type: type_annotation.clone(),
visibility: extract_visibility(node, source),
is_async: false,
is_method: false,
docstring: None,
calls: vec![],
imports: vec![],
byte_range: (child.start_byte(), child.end_byte()),
flow_facts: vec![],
cyclomatic_complexity: 0,
});
}
}
}
impl CodeIntelligence for JavaParser {
fn get_signatures(&self, source: &[u8]) -> Result<Vec<SignatureInfo>> {
let mut parser = Parser::new();
self.get_signatures_with_parser(source, &mut parser)
}
fn get_signatures_with_parser(
&self,
source: &[u8],
parser: &mut tree_sitter::Parser,
) -> Result<Vec<SignatureInfo>> {
parser
.set_language(&crate::parse::traits::languages::java::language())
.map_err(|e| Error::ParseFailed(e.to_string()))?;
let tree = parser
.parse(source, None)
.ok_or_else(|| Error::ParseFailed("Failed to parse Java source".to_string()))?;
let root_node = tree.root_node();
let imports = extract_java_imports(root_node, source);
let mut signatures = self.extract_all_definitions(source, root_node);
for sig in &mut signatures {
sig.imports = imports.clone();
}
Ok(signatures)
}
fn compute_cfg(&self, source: &[u8], node_id: usize) -> Result<Graph<Block, Edge>> {
let mut parser = Parser::new();
parser
.set_language(&crate::parse::traits::languages::java::language())
.map_err(|e| Error::ParseFailed(e.to_string()))?;
let tree = parser
.parse(source, None)
.ok_or_else(|| Error::ParseFailed("Failed to parse Java source".to_string()))?;
let root_node = tree.root_node();
let node = crate::parse::traits::find_node_by_id(&root_node, node_id)
.ok_or_else(|| Error::ParseFailed(format!("Node {} not found", node_id)))?;
let mut cfg_builder = CfgBuilder::new(source);
cfg_builder.build_from_node(&node)?;
Ok(cfg_builder.finish())
}
fn extract_complexity(&self, node: &tree_sitter::Node<'_>) -> ComplexityMetrics {
let mut complexity = ComplexityMetrics {
cyclomatic: 1,
nesting_depth: 0,
line_count: 0,
token_count: 0,
};
calculate_complexity(node, &mut complexity, 0, DECISION_KINDS);
complexity
}
}
fn extract_java_imports(root: tree_sitter::Node<'_>, source: &[u8]) -> Vec<ImportInfo> {
let mut imports = Vec::new();
fn add_import(imports: &mut Vec<ImportInfo>, path: &str, alias: Option<String>) {
let path = path.trim().trim_end_matches(';').trim();
if path.is_empty() {
return;
}
imports.push(ImportInfo {
path: path.to_string(),
alias,
});
}
fn visit(node: &tree_sitter::Node<'_>, source: &[u8], imports: &mut Vec<ImportInfo>) {
if node.kind() == "import_declaration" {
if let Ok(text) = node.utf8_text(source) {
let text = text.trim().trim_end_matches(';').trim();
let text = text
.trim_start_matches("import ")
.trim_start_matches("static ");
let alias = text.split('.').next_back().map(|s| s.to_string());
add_import(imports, text, alias);
}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
visit(&child, source, imports);
}
}
visit(&root, source, &mut imports);
imports
}
fn extract_method_signature(
node: &tree_sitter::Node<'_>,
source: &[u8],
parent_path: &[String],
) -> Option<SignatureInfo> {
let name = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string())?;
let qualified_name = if parent_path.is_empty() {
name.clone()
} else {
format!("{}.{}", parent_path.join("."), name)
};
let parameters = extract_java_parameters(node, source);
let return_type = node
.child_by_field_name("type")
.and_then(|t| t.utf8_text(source).ok())
.map(|s| s.trim().to_string());
let visibility = extract_visibility(node, source);
let calls = extract_java_calls(node, source);
Some(SignatureInfo {
name,
qualified_name,
parameters,
return_type,
visibility,
is_async: false, is_method: true,
docstring: extract_docstring(node, source),
calls,
imports: vec![],
byte_range: (node.start_byte(), node.end_byte()),
flow_facts: vec![],
cyclomatic_complexity: 0,
})
}
fn extract_constructor_signature(
node: &tree_sitter::Node<'_>,
source: &[u8],
parent_path: &[String],
) -> Option<SignatureInfo> {
let name = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string())?;
let qualified_name = if parent_path.is_empty() {
name.clone()
} else {
format!("{}.{}", parent_path.join("."), name)
};
let parameters = extract_java_parameters(node, source);
let visibility = extract_visibility(node, source);
let calls = extract_java_calls(node, source);
Some(SignatureInfo {
name,
qualified_name,
parameters,
return_type: None, visibility,
is_async: false,
is_method: true,
docstring: extract_docstring(node, source),
calls,
imports: vec![],
byte_range: (node.start_byte(), node.end_byte()),
flow_facts: vec![],
cyclomatic_complexity: 0,
})
}
fn extract_java_calls(node: &tree_sitter::Node<'_>, source: &[u8]) -> Vec<String> {
let mut calls = Vec::new();
fn find_calls(node: &tree_sitter::Node<'_>, source: &[u8], calls: &mut Vec<String>) {
match node.kind() {
"method_invocation" => {
let object = node
.child_by_field_name("object")
.or_else(|| node.child_by_field_name("scope"))
.and_then(|n| n.utf8_text(source).ok())
.map(clean_call_text);
let name = node
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(clean_call_text);
let call_name = match (object, name) {
(Some(obj), Some(method)) => format!("{}.{}", obj, method),
(_, Some(method)) => method,
_ => node
.utf8_text(source)
.ok()
.map(clean_call_text)
.unwrap_or_default(),
};
if !call_name.is_empty() {
calls.push(call_name);
}
}
"object_creation_expression" => {
if let Some(typ) = node.child_by_field_name("type") {
if let Ok(text) = typ.utf8_text(source) {
let name = clean_call_text(text);
if !name.is_empty() {
calls.push(name);
}
}
}
}
"explicit_constructor_invocation" | "constructor_invocation" => {
if let Ok(text) = node.utf8_text(source) {
let name = clean_call_text(text);
if !name.is_empty() {
calls.push(name);
}
}
}
_ => {}
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
find_calls(&child, source, calls);
}
}
find_calls(node, source, &mut calls);
calls
}
fn extract_java_parameters(node: &tree_sitter::Node<'_>, source: &[u8]) -> Vec<Parameter> {
let mut parameters = Vec::new();
if let Some(params) = node.child_by_field_name("parameters") {
let mut cursor = params.walk();
for child in params.children(&mut cursor) {
if child.kind() == "formal_parameter" {
let name = child
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source).ok())
.map(|s| s.to_string());
let type_annotation = child
.child_by_field_name("type")
.and_then(|t| t.utf8_text(source).ok())
.map(|s| s.trim().to_string());
if let Some(name_text) = name {
parameters.push(Parameter {
name: name_text,
type_annotation,
default_value: None,
});
}
}
}
}
parameters
}
fn extract_visibility(node: &tree_sitter::Node<'_>, source: &[u8]) -> Visibility {
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
if child.kind() == "modifiers" {
let mut mcursor = child.walk();
for modifier in child.children(&mut mcursor) {
if let Ok(text) = modifier.utf8_text(source) {
match text.trim() {
"public" => return Visibility::Public,
"protected" => return Visibility::Protected,
"private" => return Visibility::Private,
_ => {}
}
}
}
}
}
Visibility::Private
}
fn extract_docstring(node: &tree_sitter::Node<'_>, source: &[u8]) -> Option<String> {
let prev_sibling = node.prev_sibling();
if let Some(sibling) = prev_sibling {
if sibling.kind() == "comment" {
if let Ok(text) = sibling.utf8_text(source) {
let is_javadoc = text.starts_with("/**");
if is_javadoc {
return Some(
text.trim()
.trim_start_matches("/**")
.trim_end_matches("*/")
.trim()
.lines()
.map(|l| l.trim().trim_start_matches('*').trim())
.collect::<Vec<_>>()
.join("\n"),
);
}
}
}
}
None
}
const DECISION_KINDS: &[&str] = &[
"if_statement",
"for_statement",
"enhanced_for_statement",
"while_statement",
"do_statement",
"switch_expression",
"case_statement",
];
cfg_builder!();
cfg_loop_handler!();
impl<'a> CfgBuilder<'a> {
fn build_from_node(&mut self, node: &tree_sitter::Node<'_>) -> Result<()> {
let entry_id = self.create_block();
self.build_cfg_recursive(node, entry_id)?;
Ok(())
}
fn build_cfg_recursive(
&mut self,
node: &tree_sitter::Node<'_>,
current_block: usize,
) -> Result<()> {
match node.kind() {
"if_statement" => {
self.handle_if_statement(node, current_block)?;
}
"for_statement" | "enhanced_for_statement" | "while_statement" | "do_statement" => {
self.handle_loop_statement(node, current_block)?;
}
"switch_expression" => {
self.handle_switch_statement(node, current_block)?;
}
_ => {
if let Ok(text) = node.utf8_text(self.source) {
self.add_statement_to_block(current_block, text.to_string());
}
let mut cursor = node.walk();
for child in node.children(&mut cursor) {
self.build_cfg_recursive(&child, current_block)?;
}
}
}
Ok(())
}
fn handle_switch_statement(
&mut self,
_node: &tree_sitter::Node<'_>,
current_block: usize,
) -> Result<()> {
let merge_block = self.create_block();
let mut cursor = _node.walk();
let mut has_cases = false;
for child in _node.children(&mut cursor) {
if child.kind() == "switch_block" {
has_cases = true;
let mut ccursor = child.walk();
for case_child in child.children(&mut ccursor) {
if case_child.kind() == "switch_block_statement_group" {
let case_block = self.create_block();
self.edges.push(Edge {
from: current_block,
to: case_block,
edge_type: EdgeType::TrueBranch,
});
self.edges.push(Edge {
from: case_block,
to: merge_block,
edge_type: EdgeType::Unconditional,
});
}
}
}
}
if !has_cases {
self.edges.push(Edge {
from: current_block,
to: merge_block,
edge_type: EdgeType::Unconditional,
});
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_java_method_extraction() {
let source = b"public class Test {
public void greet(String name) {
System.out.println(\"Hello, \" + name);
}
}";
let parser = JavaParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert!(signatures.len() >= 2);
let class = signatures.iter().find(|s| s.name == "Test");
assert!(class.is_some());
let method = signatures.iter().find(|s| s.name == "greet");
assert!(method.is_some());
}
#[test]
fn test_java_class_extraction() {
let source = b"public class Person {
private String name;
private int age;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
}";
let parser = JavaParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert!(signatures.len() >= 2);
let person = signatures.iter().find(|s| s.name == "Person");
assert!(person.is_some());
}
#[test]
fn test_java_interface_extraction() {
let source = b"public interface Runnable {
void run();
default void doNothing() {}
}";
let parser = JavaParser::new();
let signatures = parser.get_signatures(source).unwrap();
assert!(!signatures.is_empty());
let runnable = signatures.iter().find(|s| s.name == "Runnable");
assert!(runnable.is_some());
assert_eq!(runnable.unwrap().return_type, Some("interface".to_string()));
}
#[test]
fn test_java_enum_extraction() {
let source = b"public enum Color {
RED, GREEN, BLUE;
}";
let parser = JavaParser::new();
let signatures = parser.get_signatures(source).unwrap();
let color = signatures.iter().find(|s| s.name == "Color");
assert!(color.is_some());
assert_eq!(color.unwrap().return_type, Some("enum".to_string()));
}
#[test]
fn test_java_visibility_modifiers() {
let source = b"public class Test {
public void publicMethod() {}
private void privateMethod() {}
protected void protectedMethod() {}
void packagePrivateMethod() {}
}";
let parser = JavaParser::new();
let signatures = parser.get_signatures(source).unwrap();
let public = signatures.iter().find(|s| s.name == "publicMethod");
assert_eq!(public.unwrap().visibility, Visibility::Public);
let private = signatures.iter().find(|s| s.name == "privateMethod");
assert_eq!(private.unwrap().visibility, Visibility::Private);
let protected = signatures.iter().find(|s| s.name == "protectedMethod");
assert_eq!(protected.unwrap().visibility, Visibility::Protected);
}
#[test]
fn test_java_complexity_calculation() {
let source = b"public void complex(int x) {
if (x > 0) {
for (int i = 0; i < x; i++) {
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}";
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_java::LANGUAGE.into())
.unwrap();
let tree = parser.parse(source, None).unwrap();
let root = tree.root_node();
let java_parser = JavaParser::new();
let metrics = java_parser.extract_complexity(&root);
assert!(metrics.cyclomatic > 1);
assert!(metrics.nesting_depth > 0);
}
}