1use std::process::Command;
18use std::str;
19
20use crate::errors::{JavaLocatorError, Result};
21use crate::info::JavaInfo;
22
23pub fn get_java_architecture(java_path: &str) -> Result<String> {
53 let output = Command::new(java_path)
55 .arg("-d64")
56 .arg("-version")
57 .output()
58 .map_err(|e| JavaLocatorError::new(format!("Failed to run Java command: {}", e)))?;
59
60 if output.status.success() {
61 return Ok("64-bit".to_string());
62 }
63
64 let output = Command::new(java_path)
66 .arg("-d32")
67 .arg("-version")
68 .output()
69 .map_err(|e| JavaLocatorError::new(format!("Failed to run Java command: {}", e)))?;
70
71 if output.status.success() {
72 return Ok("32-bit".to_string());
73 }
74
75 let output = Command::new(java_path)
77 .arg("-XshowSettings:properties")
78 .arg("-version")
79 .output()
80 .map_err(|e| JavaLocatorError::new(format!("Failed to run Java command: {}", e)))?;
81
82 let output_str = str::from_utf8(&output.stderr)?;
83
84 for line in output_str.lines() {
85 if line.contains("os.arch") {
86 let parts: Vec<&str> = line.split('=').collect();
87 if parts.len() == 2 {
88 let arch = parts[1].trim();
89 return if arch.contains("64") {
90 Ok("64-bit".to_string())
91 } else {
92 Ok("32-bit".to_string())
93 };
94 }
95 }
96 }
97
98 Ok("Unknown".to_string())
99}
100
101pub fn get_java_version(java_path: &str) -> Result<String> {
128 let output = Command::new(java_path)
129 .arg("-version")
130 .output()
131 .map_err(|e| JavaLocatorError::new(format!("Failed to run Java command: {}", e)))?;
132
133 let output_str = str::from_utf8(&output.stderr)?;
134
135 for line in output_str.lines() {
137 if line.starts_with("java version")
139 || line.starts_with("openjdk version")
140 || line.starts_with("java version")
141 || line.contains("version \"")
142 {
143 let line = line.trim();
145
146 if let Some(start) = line.find('\"') {
148 if let Some(end) = line[start + 1..].find('\"') {
149 let version = &line[start + 1..start + 1 + end];
150 return Ok(version.to_string());
151 }
152 }
153
154 let parts: Vec<&str> = line.split_whitespace().collect();
156 for (i, part) in parts.iter().enumerate() {
157 if part.contains("version") && i + 1 < parts.len() {
158 let version = parts[i + 1].trim_matches('\"');
159 return Ok(version.to_string());
160 }
161 }
162
163 if parts.len() >= 3 {
165 let version = parts[2].trim_matches('\"');
166 return Ok(version.to_string());
167 }
168 }
169 }
170
171 Err(JavaLocatorError::new(
172 "Could not determine Java version".to_string(),
173 ))
174}
175
176pub fn get_java_suppliers(java_path: &str) -> Result<String> {
203 let output = Command::new(java_path)
204 .arg("-version")
205 .output()
206 .map_err(|e| JavaLocatorError::new(format!("Failed to run Java command: {}", e)))?;
207
208 let output_str = str::from_utf8(&output.stderr)?;
209
210 for line in output_str.lines() {
212 let line_lower = line.to_lowercase();
213
214 if line_lower.contains("openjdk") && !line_lower.contains("adopt") {
215 return Ok("OpenJDK".to_string());
216 } else if line_lower.contains("oracle") {
217 return Ok("Oracle".to_string());
218 } else if line_lower.contains("ibm") {
219 return Ok("IBM".to_string());
220 } else if line_lower.contains("azul") {
221 return Ok("Azul".to_string());
222 } else if line_lower.contains("adoptopenjdk") || line_lower.contains("adoptium") {
223 return Ok("AdoptOpenJDK/Adoptium".to_string());
224 } else if line_lower.contains("amazon") || line_lower.contains("corretto") {
225 return Ok("Amazon Corretto".to_string());
226 } else if line_lower.contains("microsoft") {
227 return Ok("Microsoft".to_string());
228 } else if line_lower.contains("sap") {
229 return Ok("SAP".to_string());
230 } else if line_lower.contains("graalvm") {
231 return Ok("GraalVM".to_string());
232 } else if line_lower.contains("bellsoft") {
233 return Ok("BellSoft Liberica".to_string());
234 }
235 }
236
237 let output = Command::new(java_path)
239 .arg("-XshowSettings:properties")
240 .arg("-version")
241 .output()
242 .map_err(|e| JavaLocatorError::new(format!("Failed to run Java command: {}", e)))?;
243
244 let output_str = str::from_utf8(&output.stderr)?;
245
246 for line in output_str.lines() {
247 if line.contains("java.vendor") {
248 let parts: Vec<&str> = line.split('=').collect();
249 if parts.len() == 2 {
250 return Ok(parts[1].trim().to_string());
251 }
252 }
253 }
254
255 Ok("Unknown".to_string())
256}
257
258pub fn get_java_info(java_exec_path: &str) -> Result<JavaInfo> {
285 let version = get_java_version(java_exec_path)?;
286 let architecture = get_java_architecture(java_exec_path)?;
287 let suppliers = get_java_suppliers(java_exec_path)?;
288
289 let name = std::path::Path::new(java_exec_path)
290 .file_stem()
291 .and_then(|s| s.to_str())
292 .unwrap_or("java")
293 .to_string();
294
295 Ok(JavaInfo::new(
296 &name,
297 java_exec_path,
298 &version,
299 &architecture,
300 &suppliers,
301 ))
302}
303
304pub fn validate_java_executable(java_path: &str) -> Result<()> {
328 let path = std::path::Path::new(java_path);
329
330 if !path.exists() {
331 return Err(JavaLocatorError::new(
332 format!("Java executable not found: {}", java_path)
333 ));
334 }
335
336 let output = Command::new(java_path)
338 .arg("-version")
339 .output()
340 .map_err(|e| JavaLocatorError::new(format!("Failed to execute Java: {}", e)))?;
341
342 if !output.status.success() {
343 return Err(JavaLocatorError::new(
344 format!("Java executable failed to run: {}", java_path)
345 ));
346 }
347
348 Ok(())
349}
350
351#[cfg(test)]
352mod tests {
353 use super::*;
354
355 #[test]
357 fn test_get_java_version() {
358 if let Ok(java_home) = crate::locate_java_home() {
360 let java_exec_path = format!("{}/bin/java", java_home);
361 if std::path::Path::new(&java_exec_path).exists() {
362 let version = get_java_version(&java_exec_path);
363 assert!(version.is_ok());
364 let version_str = version.unwrap();
365 println!("Java version: {}", version_str);
366 assert!(!version_str.is_empty());
367
368 assert!(version_str.contains('.') || version_str.contains('_') ||
370 version_str.chars().any(|c| c.is_digit(10)));
371 }
372 }
373 }
374
375 #[test]
377 fn test_get_java_architecture() {
378 if let Ok(java_home) = crate::locate_java_home() {
379 let java_exec_path = format!("{}/bin/java", java_home);
380 if std::path::Path::new(&java_exec_path).exists() {
381 let arch = get_java_architecture(&java_exec_path);
382 assert!(arch.is_ok());
383 let arch_str = arch.unwrap();
384 println!("Java architecture: {}", arch_str);
385
386 assert!(arch_str == "64-bit" || arch_str == "32-bit" || arch_str == "Unknown");
388 }
389 }
390 }
391
392 #[test]
394 fn test_get_java_suppliers() {
395 if let Ok(java_home) = crate::locate_java_home() {
396 let java_exec_path = format!("{}/bin/java", java_home);
397 if std::path::Path::new(&java_exec_path).exists() {
398 let supplier = get_java_suppliers(&java_exec_path);
399 assert!(supplier.is_ok());
400 let supplier_str = supplier.unwrap();
401 println!("Java supplier: {}", supplier_str);
402 assert!(!supplier_str.is_empty());
403 }
404 }
405 }
406
407 #[test]
409 fn test_get_java_info() {
410 if let Ok(java_home) = crate::locate_java_home() {
411 let java_exec_path = format!("{}/bin/java", java_home);
412 if std::path::Path::new(&java_exec_path).exists() {
413 let info = get_java_info(&java_exec_path);
414 assert!(info.is_ok());
415 let info = info.unwrap();
416 println!("Java Info: {:?}", info);
417
418 assert!(!info.name.is_empty());
420 assert!(!info.path.is_empty());
421 assert!(!info.version.is_empty());
422 assert!(!info.architecture.is_empty());
423 assert!(!info.suppliers.is_empty());
424 }
425 }
426 }
427
428 #[test]
430 fn test_validate_java_executable() {
431 if let Ok(java_home) = crate::locate_java_home() {
433 let java_exec_path = format!("{}/bin/java", java_home);
434 if std::path::Path::new(&java_exec_path).exists() {
435 let result = validate_java_executable(&java_exec_path);
436 assert!(result.is_ok());
437 }
438 }
439
440 let result = validate_java_executable("/path/that/does/not/exist/java");
442 assert!(result.is_err());
443 }
444
445 #[test]
447 fn test_version_parsing() {
448 let test_cases = vec![
450 ("java version \"1.8.0_312\"", "1.8.0_312"),
451 ("openjdk version \"11.0.12\" 2021-07-20", "11.0.12"),
452 ("java version \"17.0.1\" 2021-10-19 LTS", "17.0.1"),
453 ("openjdk version \"1.8.0_302\"", "1.8.0_302"),
454 ];
455
456 for (input, expected) in test_cases {
459 println!("Testing version parsing: {}", input);
460 }
463 }
464
465 #[test]
467 fn test_supplier_patterns() {
468 let test_cases = vec![
469 ("OpenJDK Runtime Environment", "OpenJDK"),
470 ("Java(TM) SE Runtime Environment", "Oracle"),
471 ("IBM J9 VM", "IBM"),
472 ("Zulu", "Azul"),
473 ("AdoptOpenJDK", "AdoptOpenJDK/Adoptium"),
474 ("Corretto", "Amazon Corretto"),
475 ("Microsoft", "Microsoft"),
476 ];
477
478 for (input, expected) in test_cases {
479 println!("Testing supplier pattern: {} -> {}", input, expected);
480 }
482 }
483}