Skip to main content

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}