ricecoder_research/dependency_analyzer/
java_parser.rs1use crate::error::ResearchError;
4use crate::models::Dependency;
5use std::path::Path;
6use tracing::debug;
7
8#[derive(Debug)]
10pub struct JavaParser;
11
12impl JavaParser {
13 pub fn new() -> Self {
15 JavaParser
16 }
17
18 pub fn parse(&self, root: &Path) -> Result<Vec<Dependency>, ResearchError> {
20 let mut dependencies = Vec::new();
21
22 let pom_path = root.join("pom.xml");
24 if pom_path.exists() {
25 debug!("Parsing Java dependencies from {:?}", pom_path);
26 if let Ok(mut deps) = self.parse_pom(&pom_path) {
27 dependencies.append(&mut deps);
28 }
29 }
30
31 let gradle_path = root.join("build.gradle");
33 if gradle_path.exists() {
34 debug!("Parsing Java dependencies from {:?}", gradle_path);
35 if let Ok(mut deps) = self.parse_gradle(&gradle_path) {
36 dependencies.append(&mut deps);
37 }
38 }
39
40 Ok(dependencies)
41 }
42
43 fn parse_pom(&self, path: &Path) -> Result<Vec<Dependency>, ResearchError> {
45 let content =
46 std::fs::read_to_string(path).map_err(|e| ResearchError::DependencyParsingFailed {
47 language: "Java".to_string(),
48 path: Some(path.to_path_buf()),
49 reason: format!("Failed to read pom.xml: {}", e),
50 })?;
51
52 let mut dependencies = Vec::new();
53
54 let dep_pattern = regex::Regex::new(
57 r"<dependency>\s*<groupId>([^<]+)</groupId>\s*<artifactId>([^<]+)</artifactId>\s*<version>([^<]+)</version>(?:\s*<scope>([^<]+)</scope>)?"
58 ).unwrap();
59
60 for cap in dep_pattern.captures_iter(&content) {
61 let group_id = cap.get(1).map(|m| m.as_str()).unwrap_or("");
62 let artifact_id = cap.get(2).map(|m| m.as_str()).unwrap_or("");
63 let version = cap.get(3).map(|m| m.as_str()).unwrap_or("");
64 let scope = cap.get(4).map(|m| m.as_str()).unwrap_or("compile");
65
66 let name = format!("{}:{}", group_id, artifact_id);
67 let is_dev = scope == "test";
68
69 dependencies.push(Dependency {
70 name,
71 version: version.to_string(),
72 constraints: Some(version.to_string()),
73 is_dev,
74 });
75 }
76
77 Ok(dependencies)
78 }
79
80 fn parse_gradle(&self, path: &Path) -> Result<Vec<Dependency>, ResearchError> {
82 let content =
83 std::fs::read_to_string(path).map_err(|e| ResearchError::DependencyParsingFailed {
84 language: "Java".to_string(),
85 path: Some(path.to_path_buf()),
86 reason: format!("Failed to read build.gradle: {}", e),
87 })?;
88
89 let mut dependencies = Vec::new();
90
91 let dep_pattern = regex::Regex::new(
94 r#"(?:implementation|testImplementation|api|testApi|compileOnly|testCompileOnly)\s+['"]([^:]+):([^:]+):([^'"]+)['"]"#
95 ).unwrap();
96
97 for cap in dep_pattern.captures_iter(&content) {
98 let group_id = cap.get(1).map(|m| m.as_str()).unwrap_or("");
99 let artifact_id = cap.get(2).map(|m| m.as_str()).unwrap_or("");
100 let version = cap.get(3).map(|m| m.as_str()).unwrap_or("");
101
102 let name = format!("{}:{}", group_id, artifact_id);
103
104 dependencies.push(Dependency {
105 name,
106 version: version.to_string(),
107 constraints: Some(version.to_string()),
108 is_dev: false,
109 });
110 }
111
112 Ok(dependencies)
113 }
114
115 pub fn has_manifest(&self, root: &Path) -> bool {
117 root.join("pom.xml").exists() || root.join("build.gradle").exists()
118 }
119}
120
121impl Default for JavaParser {
122 fn default() -> Self {
123 Self::new()
124 }
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130 use std::fs;
131 use tempfile::TempDir;
132
133 #[test]
134 fn test_java_parser_creation() {
135 let parser = JavaParser::new();
136 assert!(true);
137 }
138
139 #[test]
140 fn test_java_parser_no_manifest() {
141 let parser = JavaParser::new();
142 let temp_dir = TempDir::new().unwrap();
143 let result = parser.parse(temp_dir.path()).unwrap();
144 assert!(result.is_empty());
145 }
146
147 #[test]
148 fn test_java_parser_pom_xml() {
149 let parser = JavaParser::new();
150 let temp_dir = TempDir::new().unwrap();
151
152 let pom = r#"<?xml version="1.0"?>
153<project>
154 <dependencies>
155 <dependency>
156 <groupId>junit</groupId>
157 <artifactId>junit</artifactId>
158 <version>4.13.2</version>
159 <scope>test</scope>
160 </dependency>
161 <dependency>
162 <groupId>org.springframework</groupId>
163 <artifactId>spring-core</artifactId>
164 <version>5.3.0</version>
165 </dependency>
166 </dependencies>
167</project>"#;
168
169 fs::write(temp_dir.path().join("pom.xml"), pom).unwrap();
170
171 let deps = parser.parse(temp_dir.path()).unwrap();
172 assert_eq!(deps.len(), 2);
173
174 let junit = deps.iter().find(|d| d.name.contains("junit")).unwrap();
175 assert!(junit.is_dev);
176 }
177
178 #[test]
179 fn test_java_parser_has_manifest() {
180 let parser = JavaParser::new();
181 let temp_dir = TempDir::new().unwrap();
182
183 assert!(!parser.has_manifest(temp_dir.path()));
184
185 fs::write(temp_dir.path().join("pom.xml"), "").unwrap();
186 assert!(parser.has_manifest(temp_dir.path()));
187 }
188}