use super::{MainModule, ModuleExport, ModuleOpens, ModulePatch, ModuleRead};
use ahash::{AHashMap, AHashSet};
pub use ristretto_classfile::VerifyMode;
use ristretto_classloader::ClassPath;
use ristretto_gc::GarbageCollector;
use std::fmt::Debug;
use std::io::{Read, Write};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::Mutex;
pub struct Configuration {
pub(super) class_path: ClassPath,
pub(super) main_class: Option<String>,
pub(super) jar: Option<PathBuf>,
pub(super) java_home: Option<PathBuf>,
pub(super) java_version: Option<String>,
pub(super) system_properties: AHashMap<String, String>,
pub(super) interpreted: bool,
pub(super) batch_compilation: bool,
pub(super) preview_features: bool,
pub(super) verify_mode: VerifyMode,
pub(super) garbage_collector: Option<Arc<GarbageCollector>>,
pub(super) module_path: Vec<PathBuf>,
pub(super) upgrade_module_path: Vec<PathBuf>,
pub(super) main_module: Option<MainModule>,
pub(super) add_modules: Vec<String>,
pub(super) limit_modules: AHashSet<String>,
pub(super) add_reads: Vec<ModuleRead>,
pub(super) add_exports: Vec<ModuleExport>,
pub(super) add_opens: Vec<ModuleOpens>,
pub(super) patch_modules: Vec<ModulePatch>,
pub(super) stdin: Arc<Mutex<dyn Read + Send + Sync>>,
pub(super) stdout: Arc<Mutex<dyn Write + Send + Sync>>,
pub(super) stderr: Arc<Mutex<dyn Write + Send + Sync>>,
}
impl Configuration {
#[must_use]
pub fn class_path(&self) -> &ClassPath {
&self.class_path
}
#[must_use]
pub fn main_class(&self) -> Option<&String> {
self.main_class.as_ref()
}
#[must_use]
pub fn jar(&self) -> Option<&PathBuf> {
self.jar.as_ref()
}
#[must_use]
pub fn java_home(&self) -> Option<&PathBuf> {
self.java_home.as_ref()
}
#[must_use]
pub fn java_version(&self) -> Option<&String> {
self.java_version.as_ref()
}
#[must_use]
pub fn system_properties(&self) -> &AHashMap<String, String> {
&self.system_properties
}
#[must_use]
pub fn interpreted(&self) -> bool {
self.interpreted
}
#[must_use]
pub fn preview_features(&self) -> bool {
self.preview_features
}
#[must_use]
pub fn verify_mode(&self) -> VerifyMode {
self.verify_mode
}
#[must_use]
pub fn garbage_collector(&self) -> Option<&Arc<GarbageCollector>> {
self.garbage_collector.as_ref()
}
#[must_use]
pub fn stdin(&self) -> Arc<Mutex<dyn Read + Send + Sync>> {
self.stdin.clone()
}
#[must_use]
pub fn stdout(&self) -> Arc<Mutex<dyn Write + Send + Sync>> {
self.stdout.clone()
}
#[must_use]
pub fn stderr(&self) -> Arc<Mutex<dyn Write + Send + Sync>> {
self.stderr.clone()
}
#[must_use]
pub fn batch_compilation(&self) -> bool {
self.batch_compilation
}
#[must_use]
pub fn module_path(&self) -> &[PathBuf] {
&self.module_path
}
#[must_use]
pub fn upgrade_module_path(&self) -> &[PathBuf] {
&self.upgrade_module_path
}
#[must_use]
pub fn main_module(&self) -> Option<&MainModule> {
self.main_module.as_ref()
}
#[must_use]
pub fn main_module_name(&self) -> Option<&str> {
self.main_module.as_ref().map(|m| m.name.as_str())
}
#[must_use]
pub fn main_module_class(&self) -> Option<&str> {
self.main_module
.as_ref()
.and_then(|m| m.main_class.as_deref())
}
#[must_use]
pub fn is_module_mode(&self) -> bool {
self.main_module.is_some()
}
#[must_use]
pub fn add_modules(&self) -> &[String] {
&self.add_modules
}
#[must_use]
pub fn limit_modules(&self) -> &AHashSet<String> {
&self.limit_modules
}
#[must_use]
pub fn add_reads(&self) -> &[ModuleRead] {
&self.add_reads
}
#[must_use]
pub fn add_exports(&self) -> &[ModuleExport] {
&self.add_exports
}
#[must_use]
pub fn add_opens(&self) -> &[ModuleOpens] {
&self.add_opens
}
#[must_use]
pub fn patch_modules(&self) -> &[ModulePatch] {
&self.patch_modules
}
}
#[expect(clippy::missing_fields_in_debug)]
impl Debug for Configuration {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Configuration")
.field("class_path", &self.class_path)
.field("main_class", &self.main_class)
.field("jar", &self.jar)
.field("java_home", &self.java_home)
.field("java_version", &self.java_version)
.field("system_properties", &self.system_properties)
.field("interpreted", &self.interpreted)
.field("batch_compilation", &self.batch_compilation)
.field("preview_features", &self.preview_features)
.field("verify_mode", &self.verify_mode)
.field("module_path", &self.module_path)
.field("upgrade_module_path", &self.upgrade_module_path)
.field("main_module", &self.main_module)
.field("add_modules", &self.add_modules)
.field("limit_modules", &self.limit_modules)
.field("add_reads", &self.add_reads)
.field("add_exports", &self.add_exports)
.field("add_opens", &self.add_opens)
.field("patch_modules", &self.patch_modules)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::super::ConfigurationBuilder;
use super::*;
use ristretto_classloader::DEFAULT_JAVA_VERSION;
type Result<T> = std::result::Result<T, crate::Error>;
#[test]
fn test_class_path() -> Result<()> {
let config = ConfigurationBuilder::new()
.class_path(ClassPath::from(&["/path/to/classes"]))
.build()?;
assert!(config.class_path().to_string().contains("path/to/classes"));
Ok(())
}
#[test]
fn test_main_class() -> Result<()> {
let config = ConfigurationBuilder::new()
.main_class("com.example.Main")
.build()?;
assert_eq!(Some(&"com.example.Main".to_string()), config.main_class());
Ok(())
}
#[test]
fn test_main_class_none() -> Result<()> {
let config = ConfigurationBuilder::new().build()?;
assert!(config.main_class().is_none());
Ok(())
}
#[test]
fn test_jar() -> Result<()> {
let config = ConfigurationBuilder::new()
.jar(PathBuf::from("/path/to/app.jar"))
.build()?;
assert_eq!(Some(&PathBuf::from("/path/to/app.jar")), config.jar());
Ok(())
}
#[test]
fn test_java_home() -> Result<()> {
let config = ConfigurationBuilder::new()
.java_home(PathBuf::from("/usr/lib/jvm/java-21"))
.build()?;
assert_eq!(
Some(&PathBuf::from("/usr/lib/jvm/java-21")),
config.java_home()
);
assert!(config.java_version().is_none());
Ok(())
}
#[test]
fn test_java_version() -> Result<()> {
let config = ConfigurationBuilder::new().java_version("21").build()?;
assert_eq!(Some(&"21".to_string()), config.java_version());
Ok(())
}
#[test]
fn test_default_java_version() -> Result<()> {
let config = ConfigurationBuilder::new().build()?;
assert_eq!(
Some(&DEFAULT_JAVA_VERSION.to_string()),
config.java_version()
);
Ok(())
}
#[test]
fn test_system_properties() -> Result<()> {
let config = ConfigurationBuilder::new()
.add_system_property("key1", "value1")
.add_system_property("key2", "value2")
.build()?;
let props = config.system_properties();
assert_eq!(Some(&"value1".to_string()), props.get("key1"));
assert_eq!(Some(&"value2".to_string()), props.get("key2"));
Ok(())
}
#[test]
fn test_interpreted() -> Result<()> {
let config = ConfigurationBuilder::new().interpreted(true).build()?;
assert!(config.interpreted());
Ok(())
}
#[test]
fn test_preview_features() -> Result<()> {
let config = ConfigurationBuilder::new().preview_features().build()?;
assert!(config.preview_features());
Ok(())
}
#[test]
fn test_batch_compilation() -> Result<()> {
let config = ConfigurationBuilder::new()
.batch_compilation(false)
.build()?;
assert!(!config.batch_compilation());
Ok(())
}
#[test]
fn test_verify_mode() -> Result<()> {
let config = ConfigurationBuilder::new()
.verify_mode(VerifyMode::All)
.build()?;
assert_eq!(VerifyMode::All, config.verify_mode());
Ok(())
}
#[test]
fn test_module_path() -> Result<()> {
let config = ConfigurationBuilder::new()
.module_path(vec![PathBuf::from("/mods")])
.build()?;
assert_eq!(1, config.module_path().len());
assert_eq!(PathBuf::from("/mods"), config.module_path()[0]);
Ok(())
}
#[test]
fn test_upgrade_module_path() -> Result<()> {
let config = ConfigurationBuilder::new()
.upgrade_module_path(vec![PathBuf::from("/upgrade")])
.build()?;
assert_eq!(1, config.upgrade_module_path().len());
Ok(())
}
#[test]
fn test_main_module() -> Result<()> {
let config = ConfigurationBuilder::new()
.main_module(MainModule::with_main_class("my.module", "com.example.Main"))
.build()?;
assert!(config.is_module_mode());
assert_eq!(Some("my.module"), config.main_module_name());
assert_eq!(Some("com.example.Main"), config.main_module_class());
Ok(())
}
#[test]
fn test_add_modules() -> Result<()> {
let config = ConfigurationBuilder::new()
.add_module("java.sql")
.add_module("java.xml")
.build()?;
assert_eq!(2, config.add_modules().len());
Ok(())
}
#[test]
fn test_limit_modules() -> Result<()> {
let config = ConfigurationBuilder::new()
.limit_module("java.base")
.build()?;
assert!(config.limit_modules().contains("java.base"));
Ok(())
}
#[test]
fn test_add_reads() -> Result<()> {
let config = ConfigurationBuilder::new()
.add_read(ModuleRead::new("my.module", "java.sql"))
.build()?;
assert_eq!(1, config.add_reads().len());
Ok(())
}
#[test]
fn test_add_exports() -> Result<()> {
let config = ConfigurationBuilder::new()
.add_export(ModuleExport::new("java.base", "java.lang", "ALL-UNNAMED"))
.build()?;
assert_eq!(1, config.add_exports().len());
Ok(())
}
#[test]
fn test_add_opens() -> Result<()> {
let config = ConfigurationBuilder::new()
.add_opens(ModuleOpens::new(
"java.base",
"java.lang.reflect",
"ALL-UNNAMED",
))
.build()?;
assert_eq!(1, config.add_opens().len());
Ok(())
}
#[test]
fn test_patch_modules() -> Result<()> {
let config = ConfigurationBuilder::new()
.add_patch(ModulePatch::new("java.base", "/patch"))
.build()?;
assert_eq!(1, config.patch_modules().len());
Ok(())
}
#[test]
fn test_debug() -> Result<()> {
let config = ConfigurationBuilder::new()
.main_class("com.example.Main")
.build()?;
let debug_str = format!("{config:?}");
assert!(debug_str.contains("Configuration"));
assert!(debug_str.contains("com.example.Main"));
Ok(())
}
}