Skip to main content

java_manager/
lib.rs

1// Copyright 2026 TaimWay
2//
3// @file: lib.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
17//! # Java Manager Library
18//!
19//! A comprehensive Rust library for discovering, managing, and interacting with Java installations.
20//!
21//! This library provides functionality to:
22//! - Locate Java installations across different platforms
23//! - Extract detailed information about Java installations
24//! - Execute Java commands and capture output
25//! - Manage multiple Java installations
26//! - Search for files within Java installations
27//!
28//! ## Features
29//!
30//! - **Cross-platform support**: Works on Windows, macOS, and Linux/Unix
31//! - **Comprehensive Java detection**: Finds Java installations in common locations
32//! - **Detailed Java information**: Extracts version, architecture, supplier information
33//! - **Advanced searching**: Wildcard support for file searches
34//! - **Command execution**: Execute Java commands and capture output
35//!
36//! ## Quick Start
37//!
38//! ```rust
39//! use java_manager;
40//!
41//! fn main() -> java_manager::Result<()> {
42//!     // Get detailed information about the default Java installation
43//!     let java_info = java_manager::get_local_java_home()?;
44//!     println!("Default Java: {}", java_info);
45//!
46//!     // Find all Java installations on the system
47//!     let installations = java_manager::find_all_java_installations()?;
48//!     println!("Found {} Java installations", installations.len());
49//!
50//!     // Execute a Java command
51//!     let output = java_info.execute_with_output(&["-version"])?;
52//!     println!("Java version output:\n{}", output);
53//!
54//!     Ok(())
55//! }
56//! ```
57
58use std::env;
59use std::path::PathBuf;
60use std::process::Command;
61
62use glob::{glob, Pattern};
63
64/// Error handling module
65pub mod errors;
66/// Java information structures
67pub mod info;
68/// Local Java installation management
69pub mod local;
70/// Java installation manager
71pub mod manager;
72/// Utility functions
73pub mod utils;
74
75// Re-export commonly used types and functions
76pub use errors::{JavaLocatorError, Result};
77pub use info::JavaInfo;
78pub use utils::{get_java_architecture, get_java_info, get_java_suppliers, get_java_version};
79pub use local::{
80    find_all_java_installations, get_java_document, get_java_dyn_lib,
81    get_java_home as get_local_java_home,
82};
83
84/// Returns the platform-specific name of the JVM dynamic library.
85///
86/// # Returns
87///
88/// - `"jvm.dll"` on Windows
89/// - `"libjvm.dylib"` on macOS
90/// - `"libjvm.so"` on Linux/Unix
91///
92/// # Examples
93///
94/// ```rust
95/// use java_manager;
96///
97/// let lib_name = java_manager::get_jvm_dyn_lib_file_name();
98/// println!("JVM library name: {}", lib_name);
99/// ```
100pub fn get_jvm_dyn_lib_file_name() -> &'static str {
101    if cfg!(target_os = "windows") {
102        "jvm.dll"
103    } else if cfg!(target_os = "macos") {
104        "libjvm.dylib"
105    } else {
106        "libjvm.so"
107    }
108}
109
110/// Locates and returns the Java home directory path.
111///
112/// This function first checks the `JAVA_HOME` environment variable.
113/// If not set or empty, it attempts to locate Java using platform-specific methods.
114///
115/// # Platform-specific Behavior
116///
117/// - **Windows**: Uses the `where` command to find `java.exe`
118/// - **macOS**: Uses `/usr/libexec/java_home` system utility
119/// - **Linux/Unix**: Uses the `which` command to find `java`
120///
121/// # Returns
122///
123/// - `Ok(String)` containing the Java home path
124/// - `Err(JavaLocatorError)` if Java cannot be located
125///
126/// # Errors
127///
128/// This function may return an error if:
129/// - Java is not installed
130/// - Java is not in the system PATH
131/// - The platform-specific command fails
132///
133/// # Examples
134///
135/// ```rust
136/// use java_manager;
137///
138/// fn main() -> java_manager::Result<()> {
139///     let java_home = java_manager::locate_java_home()?;
140///     println!("Java home: {}", java_home);
141///     Ok(())
142/// }
143/// ```
144pub fn locate_java_home() -> Result<String> {
145    match &env::var("JAVA_HOME") {
146        Ok(s) if s.is_empty() => do_locate_java_home(),
147        Ok(java_home_env_var) => Ok(java_home_env_var.clone()),
148        Err(_) => do_locate_java_home(),
149    }
150}
151
152#[cfg(target_os = "windows")]
153fn do_locate_java_home() -> Result<String> {
154    let output = Command::new("where")
155        .arg("java")
156        .output()
157        .map_err(|e| JavaLocatorError::new(format!("Failed to run command `where` ({e})")))?;
158
159    let java_exec_path_raw = std::str::from_utf8(&output.stdout)?;
160    java_exec_path_validation(java_exec_path_raw)?;
161
162    let paths_found = java_exec_path_raw.lines().count();
163    if paths_found > 1 {
164        eprintln!("WARNING: Found {paths_found} possible java locations. Using the first one. Set JAVA_HOME env var to avoid this warning.")
165    }
166
167    let java_exec_path = java_exec_path_raw
168        .lines()
169        .next()
170        .expect("guaranteed to have at least one line by java_exec_path_validation")
171        .trim();
172
173    let mut home_path = follow_symlinks(java_exec_path);
174
175    // Remove "bin" and parent directory to get JAVA_HOME
176    home_path.pop();
177    home_path.pop();
178
179    home_path
180        .into_os_string()
181        .into_string()
182        .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
183}
184
185#[cfg(target_os = "macos")]
186fn do_locate_java_home() -> Result<String> {
187    let output = Command::new("/usr/libexec/java_home")
188        .output()
189        .map_err(|e| {
190            JavaLocatorError::new(format!(
191                "Failed to run command `/usr/libexec/java_home` ({e})"
192            ))
193        })?;
194
195    let java_exec_path = std::str::from_utf8(&output.stdout)?.trim();
196
197    java_exec_path_validation(java_exec_path)?;
198    let home_path = follow_symlinks(java_exec_path);
199
200    home_path
201        .into_os_string()
202        .into_string()
203        .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
204}
205
206#[cfg(not(any(target_os = "windows", target_os = "macos")))] // Unix
207fn do_locate_java_home() -> Result<String> {
208    let output = Command::new("which")
209        .arg("java")
210        .output()
211        .map_err(|e| JavaLocatorError::new(format!("Failed to run command `which` ({e})")))?;
212    let java_exec_path = std::str::from_utf8(&output.stdout)?.trim();
213
214    java_exec_path_validation(java_exec_path)?;
215    let mut home_path = follow_symlinks(java_exec_path);
216
217    // Remove "bin" directory to get JAVA_HOME
218    home_path.pop();
219    home_path.pop();
220
221    home_path
222        .into_os_string()
223        .into_string()
224        .map_err(|path| JavaLocatorError::new(format!("Java path {path:?} is invalid utf8")))
225}
226
227/// Validates that a Java executable path is not empty.
228///
229/// # Arguments
230///
231/// * `path` - The Java executable path to validate
232///
233/// # Returns
234///
235/// - `Ok(())` if the path is valid (non-empty)
236/// - `Err(JavaLocatorError)` if the path is empty
237///
238/// # Examples
239///
240/// ```rust
241/// use java_manager;
242///
243/// let result = java_manager::locate_java_home();
244/// assert!(result.is_ok() || result.is_err());
245/// ```
246fn java_exec_path_validation(path: &str) -> Result<()> {
247    if path.is_empty() {
248        return Err(JavaLocatorError::new(
249            "Java is not installed or not in the system PATH".into(),
250        ));
251    }
252
253    Ok(())
254}
255
256/// Follows symbolic links to get the real path of an executable.
257///
258/// # Arguments
259///
260/// * `path` - The path to follow
261///
262/// # Returns
263///
264/// A `PathBuf` containing the real path after following all symbolic links
265///
266/// # Examples
267///
268/// ```rust
269/// use std::path::PathBuf;
270///
271/// let real_path = java_manager::locate_java_home().unwrap();
272/// println!("Real path: {:?}", real_path);
273/// ```
274fn follow_symlinks(path: &str) -> PathBuf {
275    let mut test_path = PathBuf::from(path);
276    while let Ok(path) = test_path.read_link() {
277        test_path = if path.is_absolute() {
278            path
279        } else {
280            test_path.pop();
281            test_path.push(path);
282            test_path
283        };
284    }
285    test_path
286}
287
288/// Locates the JVM dynamic library directory.
289///
290/// Searches for the JVM dynamic library (jvm.dll, libjvm.dylib, or libjvm.so)
291/// within the Java installation directory.
292///
293/// # Returns
294///
295/// - `Ok(String)` containing the directory path where the JVM library is located
296/// - `Err(JavaLocatorError)` if the JVM library cannot be found
297///
298/// # Examples
299///
300/// ```rust
301/// use java_manager;
302///
303/// fn main() -> java_manager::Result<()> {
304///     let jvm_lib_path = java_manager::locate_jvm_dyn_library()?;
305///     println!("JVM library directory: {}", jvm_lib_path);
306///     Ok(())
307/// }
308/// ```
309pub fn locate_jvm_dyn_library() -> Result<String> {
310    if cfg!(target_os = "windows") {
311        locate_file("jvm.dll")
312    } else {
313        locate_file("libjvm.*")
314    }
315}
316
317/// Searches for a file within the Java installation directory.
318///
319/// Supports wildcard patterns in the file name.
320///
321/// # Arguments
322///
323/// * `file_name` - The name of the file to search for (supports wildcards)
324///
325/// # Returns
326///
327/// - `Ok(String)` containing the directory path where the file is located
328/// - `Err(JavaLocatorError)` if the file cannot be found
329///
330/// # Examples
331///
332/// ```rust
333/// use java_manager;
334///
335/// fn main() -> java_manager::Result<()> {
336///     // Find libjsig.so
337///     let libjsig_dir = java_manager::locate_file("libjsig.so")?;
338///     println!("libjsig.so directory: {}", libjsig_dir);
339///
340///     // Search with wildcard
341///     let jvm_lib_dir = java_manager::locate_file("libjvm*")?;
342///     println!("JVM library directory: {}", jvm_lib_dir);
343///
344///     Ok(())
345/// }
346/// ```
347pub fn locate_file(file_name: &str) -> Result<String> {
348    let java_home = locate_java_home()?;
349
350    let query = format!("{}/**/{}", Pattern::escape(&java_home), file_name);
351
352    let path = glob(&query)?.filter_map(|x| x.ok()).next().ok_or_else(|| {
353        JavaLocatorError::new(format!(
354            "Could not find the {file_name} library in any subdirectory of {java_home}",
355        ))
356    })?;
357
358    let parent_path = path.parent().unwrap();
359    match parent_path.to_str() {
360        Some(parent_path) => Ok(parent_path.to_owned()),
361        None => Err(JavaLocatorError::new(format!(
362            "Java path {parent_path:?} is invalid utf8"
363        ))),
364    }
365}
366
367#[cfg(test)]
368mod unit_tests {
369    use super::*;
370
371    /// Tests basic Java home location functionality
372    #[test]
373    fn test_locate_java_home() {
374        match locate_java_home() {
375            Ok(path) => {
376                println!("Java home: {}", path);
377                // Verify the path exists
378                assert!(std::path::Path::new(&path).exists());
379            }
380            Err(e) => {
381                println!("Error locating Java home: {}", e);
382                // If Java is not installed, this test should pass (no panic)
383            }
384        }
385    }
386
387    /// Tests JVM dynamic library location functionality
388    #[test]
389    fn test_locate_jvm_dyn_library() {
390        match locate_jvm_dyn_library() {
391            Ok(path) => {
392                println!("JVM library path: {}", path);
393                // Verify the directory exists
394                assert!(std::path::Path::new(&path).exists());
395            }
396            Err(e) => {
397                println!("Error locating JVM library: {}", e);
398                // If Java is not installed or JVM library not found, this test should pass
399            }
400        }
401    }
402
403    /// Tests file searching with wildcards
404    #[test]
405    fn test_locate_file_with_wildcard() {
406        // This test requires a Java installation
407        if let Ok(java_home) = locate_java_home() {
408            // Search for Java executable
409            let java_exec = if cfg!(target_os = "windows") {
410                "java.exe"
411            } else {
412                "java"
413            };
414            
415            match locate_file(java_exec) {
416                Ok(path) => {
417                    println!("Found {} in: {}", java_exec, path);
418                    assert!(std::path::Path::new(&path).exists());
419                }
420                Err(e) => {
421                    println!("Error locating {}: {}", java_exec, e);
422                }
423            }
424        }
425    }
426
427    /// Tests platform-specific library name function
428    #[test]
429    fn test_get_jvm_dyn_lib_file_name() {
430        let lib_name = get_jvm_dyn_lib_file_name();
431        println!("Platform JVM library name: {}", lib_name);
432        
433        // Verify platform-specific names
434        if cfg!(target_os = "windows") {
435            assert_eq!(lib_name, "jvm.dll");
436        } else if cfg!(target_os = "macos") {
437            assert_eq!(lib_name, "libjvm.dylib");
438        } else {
439            assert_eq!(lib_name, "libjvm.so");
440        }
441    }
442
443    /// Tests symbolic link following functionality
444    #[test]
445    fn test_follow_symlinks() {
446        // Create a test file structure with symlinks
447        let temp_dir = tempfile::tempdir().unwrap();
448        let target_path = temp_dir.path().join("target.txt");
449        std::fs::write(&target_path, "test content").unwrap();
450        
451        let link_path = temp_dir.path().join("link.txt");
452        #[cfg(unix)]
453        std::os::unix::fs::symlink(&target_path, &link_path).unwrap();
454        
455        #[cfg(windows)]
456        std::os::windows::fs::symlink_file(&target_path, &link_path).unwrap();
457        
458        let followed = follow_symlinks(link_path.to_str().unwrap());
459        assert!(followed.exists());
460    }
461}