java_manager/local.rs
1// Copyright 2026 TaimWay
2//
3// @file: local.rs
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17use crate::errors::{JavaLocatorError, Result};
18use crate::info::JavaInfo;
19
20/// Gets detailed information about the current Java installation.
21///
22/// This function locates the Java home directory and creates a comprehensive
23/// `JavaInfo` object with all available information about the installation.
24///
25/// # Returns
26///
27/// - `Ok(JavaInfo)` containing detailed Java information
28/// - `Err(JavaLocatorError)` if Java cannot be located or information cannot be gathered
29///
30/// # Examples
31///
32/// ```rust
33/// use java_manager;
34///
35/// fn main() -> java_manager::Result<()> {
36/// let java_info = java_manager::get_local_java_home()?;
37/// println!("Current Java: {}", java_info);
38/// println!("Version: {}", java_info.version);
39/// println!("Architecture: {}", java_info.architecture);
40/// println!("Supplier: {}", java_info.suppliers);
41/// Ok(())
42/// }
43/// ```
44pub fn get_java_home() -> Result<JavaInfo> {
45 let java_home = crate::locate_java_home()?;
46
47 let java_exec_path = if cfg!(target_os = "windows") {
48 format!("{}\\bin\\java.exe", java_home)
49 } else {
50 format!("{}/bin/java", java_home)
51 };
52
53 if !std::path::Path::new(&java_exec_path).exists() {
54 return Err(JavaLocatorError::new(
55 format!("Java executable not found at: {}", java_exec_path)
56 ));
57 }
58
59 crate::utils::get_java_info(&java_exec_path)
60}
61
62/// Gets the directory containing the JVM dynamic library.
63///
64/// # Returns
65///
66/// - `Ok(String)` containing the directory path
67/// - `Err(JavaLocatorError)` if the JVM library cannot be found
68///
69/// # Examples
70///
71/// ```rust
72/// use java_manager;
73///
74/// fn main() -> java_manager::Result<()> {
75/// let jvm_lib_dir = java_manager::get_java_dyn_lib()?;
76/// println!("JVM library directory: {}", jvm_lib_dir);
77/// Ok(())
78/// }
79/// ```
80pub fn get_java_dyn_lib() -> Result<String> {
81 crate::locate_jvm_dyn_library()
82}
83
84/// Gets the Java documentation directory.
85///
86/// Searches for Java documentation in common locations within the Java installation.
87///
88/// # Returns
89///
90/// - `Ok(String)` containing the documentation directory path
91/// - `Err(JavaLocatorError)` if documentation cannot be found
92///
93/// # Examples
94///
95/// ```rust
96/// use java_manager;
97///
98/// fn main() -> java_manager::Result<()> {
99/// let doc_dir = java_manager::get_java_document()?;
100/// println!("Java documentation directory: {}", doc_dir);
101/// Ok(())
102/// }
103/// ```
104pub fn get_java_document() -> Result<String> {
105 let java_home = crate::locate_java_home()?;
106
107 // Common documentation directory names across different Java distributions
108 let possible_doc_paths = vec![
109 format!("{}/docs", java_home),
110 format!("{}/doc", java_home),
111 format!("{}/legal", java_home),
112 format!("{}/man", java_home),
113 format!("{}/man/man1", java_home),
114 format!("{}/../docs", java_home), // Some distributions install docs in parent directory
115 format!("{}/../legal", java_home),
116 ];
117
118 for path in possible_doc_paths {
119 if std::path::Path::new(&path).exists() {
120 return Ok(path);
121 }
122 }
123
124 // If no documentation directory found, return Java home
125 Ok(java_home)
126}
127
128/// Discovers all Java installations on the system.
129///
130/// Searches for Java installations in common locations and environment variables.
131/// The results are sorted by version (highest first).
132///
133/// # Returns
134///
135/// - `Ok(Vec<JavaInfo>)` containing all found Java installations
136/// - `Err(JavaLocatorError)` if an error occurs during discovery
137///
138/// # Examples
139///
140/// ```rust
141/// use java_manager;
142///
143/// fn main() -> java_manager::Result<()> {
144/// let installations = java_manager::find_all_java_installations()?;
145/// println!("Found {} Java installations:", installations.len());
146/// for (i, java) in installations.iter().enumerate() {
147/// println!("{}. {}", i + 1, java);
148/// }
149/// Ok(())
150/// }
151/// ```
152pub fn find_all_java_installations() -> Result<Vec<JavaInfo>> {
153 let mut java_installations = Vec::new();
154
155 // Check JAVA_HOME environment variable first
156 if let Ok(java_home) = std::env::var("JAVA_HOME") {
157 if !java_home.is_empty() {
158 let java_exec = if cfg!(target_os = "windows") {
159 format!("{}\\bin\\java.exe", java_home)
160 } else {
161 format!("{}/bin/java", java_home)
162 };
163
164 if std::path::Path::new(&java_exec).exists() {
165 if let Ok(info) = crate::utils::get_java_info(&java_exec) {
166 java_installations.push(info);
167 }
168 }
169 }
170 }
171
172 // Search in platform-specific common installation directories
173 let common_paths = get_platform_specific_java_paths();
174
175 for base_path in common_paths {
176 if let Ok(entries) = std::fs::read_dir(base_path) {
177 for entry in entries.flatten() {
178 let path = entry.path();
179 if path.is_dir() {
180 // Try to find Java executable in this directory
181 if let Some(java_info) = try_get_java_info_from_dir(&path) {
182 if !java_installations.iter().any(|i| i.path == java_info.path) {
183 java_installations.push(java_info);
184 }
185 }
186 }
187 }
188 }
189 }
190
191 // Also check PATH for Java executables
192 find_java_in_path(&mut java_installations);
193
194 // Sort installations by version (highest first)
195 java_installations.sort_by(|a: &JavaInfo, b: &JavaInfo| {
196 let ver_a = a.get_major_version().unwrap_or(0);
197 let ver_b = b.get_major_version().unwrap_or(0);
198 ver_b.cmp(&ver_a).then_with(|| a.path.cmp(&b.path))
199 });
200
201 Ok(java_installations)
202}
203
204/// Returns platform-specific common Java installation paths.
205///
206/// # Returns
207///
208/// Vector of directory paths where Java is commonly installed
209fn get_platform_specific_java_paths() -> Vec<&'static str> {
210 if cfg!(target_os = "windows") {
211 vec![
212 "C:\\Program Files\\Java",
213 "C:\\Program Files (x86)\\Java",
214 "C:\\java",
215 "C:\\jdk",
216 "C:\\jre",
217 ]
218 } else if cfg!(target_os = "macos") {
219 vec![
220 "/Library/Java/JavaVirtualMachines",
221 "/System/Library/Java/JavaVirtualMachines",
222 "/usr/local/opt", // Homebrew installations
223 "/opt",
224 ]
225 } else {
226 // Linux/Unix
227 vec![
228 "/usr/lib/jvm",
229 "/usr/java",
230 "/opt/java",
231 "/usr/local/java",
232 "/opt",
233 "/usr/lib",
234 ]
235 }
236}
237
238/// Attempts to get JavaInfo from a directory that might contain a Java installation.
239///
240/// # Arguments
241///
242/// * `dir_path` - Directory path that might contain a Java installation
243///
244/// # Returns
245///
246/// `Some(JavaInfo)` if a valid Java installation is found, `None` otherwise
247fn try_get_java_info_from_dir(dir_path: &std::path::Path) -> Option<JavaInfo> {
248 // Try different possible executable paths
249 let possible_exec_paths = if cfg!(target_os = "windows") {
250 vec![
251 dir_path.join("bin").join("java.exe"),
252 dir_path.join("jre").join("bin").join("java.exe"),
253 dir_path.join("bin").join("javaw.exe"),
254 ]
255 } else {
256 vec![
257 dir_path.join("bin").join("java"),
258 dir_path.join("jre").join("bin").join("java"),
259 dir_path.join("Contents").join("Home").join("bin").join("java"), // macOS .app bundles
260 ]
261 };
262
263 for exec_path in possible_exec_paths {
264 if exec_path.exists() {
265 if let Ok(info) = crate::utils::get_java_info(exec_path.to_str().unwrap()) {
266 return Some(info);
267 }
268 }
269 }
270
271 None
272}
273
274/// Searches for Java installations in the system PATH.
275///
276/// # Arguments
277///
278/// * `java_installations` - Mutable reference to vector to add found installations
279fn find_java_in_path(java_installations: &mut Vec<JavaInfo>) {
280 if let Ok(path_var) = std::env::var("PATH") {
281 for path_dir in path_var.split(std::path::MAIN_SEPARATOR) {
282 let java_exec = if cfg!(target_os = "windows") {
283 std::path::Path::new(path_dir).join("java.exe")
284 } else {
285 std::path::Path::new(path_dir).join("java")
286 };
287
288 if java_exec.exists() {
289 if let Ok(info) = crate::utils::get_java_info(java_exec.to_str().unwrap()) {
290 if !java_installations.iter().any(|i| i.path == info.path) {
291 java_installations.push(info);
292 }
293 }
294 }
295 }
296 }
297}
298
299/// Gets information about a specific Java installation by version.
300///
301/// # Arguments
302///
303/// * `major_version` - Major version of Java to find (e.g., 8, 11, 17)
304///
305/// # Returns
306///
307/// - `Ok(JavaInfo)` if a matching Java installation is found
308/// - `Err(JavaLocatorError)` if no matching installation is found
309///
310/// # Examples
311///
312/// ```rust
313/// use java_manager;
314///
315/// fn main() -> java_manager::Result<()> {
316/// // Find Java 11 installation
317/// let java_11 = java_manager::get_java_by_version(11)?;
318/// println!("Java 11: {}", java_11);
319/// Ok(())
320/// }
321/// ```
322pub fn get_java_by_version(major_version: u32) -> Result<JavaInfo> {
323 let installations = find_all_java_installations()?;
324
325 for installation in installations {
326 if let Some(version) = installation.get_major_version() {
327 if version == major_version {
328 return Ok(installation);
329 }
330 }
331 }
332
333 Err(JavaLocatorError::new(
334 format!("No Java installation found for version {}", major_version)
335 ))
336}
337
338/// Gets the latest Java installation available on the system.
339///
340/// # Returns
341///
342/// - `Ok(JavaInfo)` for the Java installation with the highest version
343/// - `Err(JavaLocatorError)` if no Java installations are found
344///
345/// # Examples
346///
347/// ```rust
348/// use java_manager;
349///
350/// fn main() -> java_manager::Result<()> {
351/// let latest_java = java_manager::get_latest_java()?;
352/// println!("Latest Java: {}", latest_java);
353/// Ok(())
354/// }
355/// ```
356pub fn get_latest_java() -> Result<JavaInfo> {
357 let installations = find_all_java_installations()?;
358
359 if installations.is_empty() {
360 return Err(JavaLocatorError::new(
361 "No Java installations found".to_string()
362 ));
363 }
364
365 // Installations are already sorted by version (highest first)
366 Ok(installations[0].clone())
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 /// Tests getting detailed Java home information
374 #[test]
375 fn test_get_java_home_info() {
376 let result = get_java_home();
377 match result {
378 Ok(info) => {
379 println!("Found Java: {}", info);
380 assert!(!info.version.is_empty());
381 assert!(!info.architecture.is_empty());
382 assert!(!info.suppliers.is_empty());
383 assert!(info.is_valid());
384 }
385 Err(e) => {
386 println!("Error getting Java home info: {}", e);
387 // If Java is not installed, this test should pass without panic
388 }
389 }
390 }
391
392 /// Tests getting JVM dynamic library directory
393 #[test]
394 fn test_get_java_dyn_lib() {
395 let result = get_java_dyn_lib();
396 match result {
397 Ok(path) => {
398 println!("JVM Dynamic Library: {}", path);
399 assert!(std::path::Path::new(&path).exists());
400 }
401 Err(e) => {
402 println!("Error getting JVM library: {}", e);
403 // If Java JVM library not found, this test should pass
404 }
405 }
406 }
407
408 /// Tests getting Java documentation directory
409 #[test]
410 fn test_get_java_document() {
411 let result = get_java_document();
412 match result {
413 Ok(path) => {
414 println!("Java documentation: {}", path);
415 // Documentation directory might not exist in all installations
416 if std::path::Path::new(&path).exists() {
417 println!("Documentation directory exists");
418 } else {
419 println!("Documentation directory does not exist, using Java home");
420 }
421 }
422 Err(e) => {
423 println!("Error getting Java document: {}", e);
424 }
425 }
426 }
427
428 /// Tests finding all Java installations
429 #[test]
430 fn test_find_all_java() {
431 let result = find_all_java_installations();
432 assert!(result.is_ok());
433 let installations = result.unwrap();
434 println!("Found {} Java installations", installations.len());
435
436 for (i, java) in installations.iter().enumerate() {
437 println!("{}. {}", i + 1, java);
438 assert!(java.is_valid());
439 }
440
441 // If Java is installed, there should be at least one installation
442 if installations.is_empty() {
443 println!("No Java installations found");
444 }
445 }
446
447 /// Tests getting Java by specific version
448 #[test]
449 fn test_get_java_by_version() {
450 // First find all installations to see what versions are available
451 if let Ok(installations) = find_all_java_installations() {
452 if !installations.is_empty() {
453 // Try to get the highest version available
454 let highest_version = installations[0].get_major_version().unwrap_or(0);
455 if highest_version > 0 {
456 let result = get_java_by_version(highest_version);
457 assert!(result.is_ok());
458 let java = result.unwrap();
459 assert_eq!(java.get_major_version().unwrap_or(0), highest_version);
460 println!("Found Java {}: {}", highest_version, java);
461 }
462
463 // Test with a version that likely doesn't exist
464 let non_existent_version = 99;
465 let result = get_java_by_version(non_existent_version);
466 assert!(result.is_err());
467 }
468 }
469 }
470
471 /// Tests getting the latest Java installation
472 #[test]
473 fn test_get_latest_java() {
474 let result = get_latest_java();
475 match result {
476 Ok(java) => {
477 println!("Latest Java: {}", java);
478 assert!(java.is_valid());
479
480 // Verify it's actually the latest by comparing with all installations
481 let all_java = find_all_java_installations().unwrap();
482 if all_java.len() > 1 {
483 let latest_version = java.get_major_version().unwrap_or(0);
484 for other_java in &all_java[1..] {
485 let other_version = other_java.get_major_version().unwrap_or(0);
486 assert!(latest_version >= other_version);
487 }
488 }
489 }
490 Err(e) => {
491 println!("Error getting latest Java: {}", e);
492 // If no Java is installed, this is expected
493 }
494 }
495 }
496
497 /// Tests platform-specific path detection
498 #[test]
499 fn test_platform_specific_paths() {
500 let paths = get_platform_specific_java_paths();
501 assert!(!paths.is_empty());
502
503 println!("Platform-specific Java paths:");
504 for path in &paths {
505 println!(" - {}", path);
506 }
507
508 // Verify platform-specific paths
509 if cfg!(target_os = "windows") {
510 assert!(paths.contains(&"C:\\Program Files\\Java"));
511 } else if cfg!(target_os = "macos") {
512 assert!(paths.contains(&"/Library/Java/JavaVirtualMachines"));
513 } else {
514 assert!(paths.contains(&"/usr/lib/jvm"));
515 }
516 }
517
518 /// Tests that Java installations are sorted correctly
519 #[test]
520 fn test_installation_sorting() {
521 if let Ok(mut installations) = find_all_java_installations() {
522 if installations.len() > 1 {
523 // Verify sorting (highest version first)
524 for i in 0..installations.len() - 1 {
525 let current_version = installations[i].get_major_version().unwrap_or(0);
526 let next_version = installations[i + 1].get_major_version().unwrap_or(0);
527 assert!(current_version >= next_version);
528 }
529 }
530 }
531 }
532
533 /// Tests that duplicate installations are filtered
534 #[test]
535 fn test_no_duplicate_installations() {
536 if let Ok(installations) = find_all_java_installations() {
537 let mut seen_paths = std::collections::HashSet::new();
538
539 for installation in &installations {
540 let path = &installation.path;
541 assert!(!seen_paths.contains(path), "Duplicate installation found: {}", path);
542 seen_paths.insert(path);
543 }
544 }
545 }
546}