use anyhow::Result;
use serde::Serialize;
use std::collections::HashMap;
use tree_sitter::Language;
use crate::core::graph::Edge;
use crate::core::parser::SupportedLanguage;
use crate::languages::{make_edge, resolve_scope_id, LanguagePlugin};
pub struct JavaPlugin;
impl LanguagePlugin for JavaPlugin {
fn language(&self) -> SupportedLanguage {
SupportedLanguage::Java
}
fn extensions(&self) -> &[&str] {
&["java"]
}
fn ts_language(&self) -> Language {
tree_sitter_java::language()
}
fn symbol_query_source(&self) -> &str {
include_str!("../queries/java/symbols.scm")
}
fn edge_query_source(&self) -> &str {
include_str!("../queries/java/edges.scm")
}
fn infer_symbol_kind(&self, node_kind: &str) -> &str {
match node_kind {
"class_declaration" => "class",
"interface_declaration" => "interface",
"enum_declaration" => "enum",
"record_declaration" => "class",
"method_declaration" => "method",
"constructor_declaration" => "method",
"field_declaration" => "property",
"annotation_type_declaration" => "type",
"enum_constant" => "variant",
_ => "function",
}
}
fn scope_node_types(&self) -> &[&str] {
&[
"class_declaration",
"interface_declaration",
"enum_declaration",
"method_declaration",
"constructor_declaration",
"lambda_expression",
]
}
fn class_body_node_types(&self) -> &[&str] {
&["class_body", "interface_body", "enum_body"]
}
fn class_decl_node_types(&self) -> &[&str] {
&[
"class_declaration",
"interface_declaration",
"enum_declaration",
]
}
fn extract_metadata(
&self,
node: &tree_sitter::Node,
source: &str,
kind: &str,
) -> Result<String> {
extract_metadata(node, source, kind)
}
fn extract_edge(
&self,
pattern_index: usize,
captures: &HashMap<String, (String, u32)>,
file_path: &str,
enclosing_scope_id: Option<&str>,
) -> Vec<Edge> {
extract_java_edge(pattern_index, captures, file_path, enclosing_scope_id)
}
fn extract_docstring(&self, node: &tree_sitter::Node, source: &str) -> Option<String> {
let prev = node.prev_sibling()?;
match prev.kind() {
"block_comment" | "line_comment" => {
let text = prev.utf8_text(source.as_bytes()).ok()?;
Some(text.trim().to_string())
}
_ => None,
}
}
fn generic_name_stopwords(&self) -> &[&str] {
&[
"toString", "hashCode", "equals", "get", "set", "of", "main", "run", "close",
]
}
}
#[derive(Debug, Clone, Serialize, Default)]
pub struct JavaMetadata {
pub access: String,
pub is_static: bool,
pub is_final: bool,
pub is_abstract: bool,
pub is_synchronized: bool,
pub annotations: Vec<String>,
pub return_type: Option<String>,
pub parameters: Vec<JavaParameterInfo>,
pub throws: Vec<String>,
}
#[derive(Debug, Clone, Serialize)]
pub struct JavaParameterInfo {
pub name: String,
#[serde(rename = "type")]
pub type_annotation: Option<String>,
pub is_final: bool,
}
pub fn extract_metadata(node: &tree_sitter::Node, source: &str, kind: &str) -> Result<String> {
let mut meta = JavaMetadata::default();
let mut child_cursor = node.walk();
for child in node.children(&mut child_cursor) {
if child.kind() == "modifiers" {
let mut mod_cursor = child.walk();
for mod_child in child.children(&mut mod_cursor) {
match mod_child.kind() {
"public" => meta.access = "public".to_string(),
"protected" => meta.access = "protected".to_string(),
"private" => meta.access = "private".to_string(),
"static" => meta.is_static = true,
"final" => meta.is_final = true,
"abstract" => meta.is_abstract = true,
"synchronized" => meta.is_synchronized = true,
"marker_annotation" | "annotation" => {
if let Ok(text) = mod_child.utf8_text(source.as_bytes()) {
let ann_name = text
.trim_start_matches('@')
.split('(')
.next()
.unwrap_or("")
.trim()
.to_string();
if !ann_name.is_empty() {
meta.annotations.push(ann_name);
}
}
}
_ => {}
}
}
}
}
if meta.access.is_empty() {
meta.access = "package".to_string();
}
if kind == "method" {
if let Some(type_node) = node.child_by_field_name("type") {
if let Ok(text) = type_node.utf8_text(source.as_bytes()) {
meta.return_type = Some(text.trim().to_string());
}
}
}
if kind == "method" {
if let Some(params_node) = node.child_by_field_name("parameters") {
meta.parameters = extract_parameters(¶ms_node, source);
}
}
let mut throws_cursor = node.walk();
for child in node.children(&mut throws_cursor) {
if child.kind() == "throws" {
let mut tc = child.walk();
for throw_child in child.children(&mut tc) {
if throw_child.kind() == "type_identifier" {
if let Ok(text) = throw_child.utf8_text(source.as_bytes()) {
meta.throws.push(text.trim().to_string());
}
}
}
}
}
let json = serde_json::to_string(&meta)?;
Ok(json)
}
fn extract_parameters(params_node: &tree_sitter::Node, source: &str) -> Vec<JavaParameterInfo> {
let mut params = Vec::new();
let mut cursor = params_node.walk();
for child in params_node.children(&mut cursor) {
if child.kind() == "formal_parameter" || child.kind() == "spread_parameter" {
let name = child
.child_by_field_name("name")
.and_then(|n| n.utf8_text(source.as_bytes()).ok())
.unwrap_or_default()
.to_string();
let type_annotation = child
.child_by_field_name("type")
.and_then(|n| n.utf8_text(source.as_bytes()).ok())
.map(|t| t.trim().to_string());
let mut is_final = false;
let mut param_cursor = child.walk();
for param_child in child.children(&mut param_cursor) {
if param_child.kind() == "modifiers" {
let mut mc = param_child.walk();
for m in param_child.children(&mut mc) {
if m.kind() == "final" {
is_final = true;
}
}
}
}
if !name.is_empty() {
params.push(JavaParameterInfo {
name,
type_annotation,
is_final,
});
}
}
}
params
}
fn extract_java_edge(
pattern: usize,
captures: &HashMap<String, (String, u32)>,
file_path: &str,
enclosing_scope_id: Option<&str>,
) -> Vec<Edge> {
let mut edges = Vec::new();
let from_fn = resolve_scope_id(enclosing_scope_id, file_path, "function");
let from_cls = resolve_scope_id(enclosing_scope_id, file_path, "class");
match pattern {
0 => {
if let Some((imported_name, line)) = captures.get("imported_name") {
edges.push(make_edge(
format!("{file_path}::__module__::function"),
imported_name,
"imports",
file_path,
*line,
));
}
}
1 => {
if let (Some((object, line)), Some((method, _))) =
(captures.get("object"), captures.get("method"))
{
edges.push(make_edge(
from_fn.clone(),
format!("{object}.{method}"),
"calls",
file_path,
*line,
));
}
}
2 => {
if let Some((callee, line)) = captures.get("callee") {
edges.push(make_edge(
from_fn.clone(),
callee,
"calls",
file_path,
*line,
));
}
}
3 | 10 => {
if let Some((method, line)) = captures.get("method") {
edges.push(make_edge(
from_fn.clone(),
method,
"calls",
file_path,
*line,
));
}
}
4 => {
if let Some((class_name, line)) = captures.get("class_name") {
edges.push(make_edge(
from_fn.clone(),
class_name,
"instantiates",
file_path,
*line,
));
}
}
5 => {
if let Some((base_type, line)) = captures.get("base_type") {
edges.push(make_edge(
from_cls.clone(),
base_type,
"extends",
file_path,
*line,
));
}
}
6 => {
if let Some((base_type, line)) = captures.get("base_type") {
edges.push(make_edge(
from_cls.clone(),
base_type,
"implements",
file_path,
*line,
));
}
}
7 => {
if let Some((base_type, line)) = captures.get("base_type") {
edges.push(make_edge(
from_cls.clone(),
base_type,
"extends",
file_path,
*line,
));
}
}
8 | 9 => {
if let Some((type_ref, line)) = captures.get("type_ref") {
edges.push(make_edge(
from_fn.clone(),
type_ref,
"references_type",
file_path,
*line,
));
}
}
11 => {
if let Some((variant_ref, line)) = captures.get("variant_ref") {
edges.push(make_edge(
from_fn.clone(),
variant_ref,
"references",
file_path,
*line,
));
}
}
_ => {}
}
edges
}