use std::path::PathBuf;
use std::collections::HashMap;
use crate::model::profile::{Profile, Activation, ActivationOS, ActivationProperty, ActivationFile};
#[derive(Debug, Clone)]
pub struct ProfileActivationContext {
pub active_profile_ids: Vec<String>,
pub inactive_profile_ids: Vec<String>,
pub system_properties: HashMap<String, String>,
pub user_properties: HashMap<String, String>,
pub project_properties: HashMap<String, String>,
pub project_directory: Option<PathBuf>,
}
impl ProfileActivationContext {
pub fn new() -> Self {
Self {
active_profile_ids: Vec::new(),
inactive_profile_ids: Vec::new(),
system_properties: Self::get_system_properties(),
user_properties: HashMap::new(),
project_properties: HashMap::new(),
project_directory: None,
}
}
pub fn with_active_profiles(mut self, profiles: Vec<String>) -> Self {
self.active_profile_ids = profiles;
self
}
pub fn with_user_properties(mut self, properties: HashMap<String, String>) -> Self {
self.user_properties = properties;
self
}
pub fn with_project_properties(mut self, properties: HashMap<String, String>) -> Self {
self.project_properties = properties;
self
}
pub fn with_project_directory(mut self, directory: PathBuf) -> Self {
self.project_directory = Some(directory);
self
}
fn get_system_properties() -> HashMap<String, String> {
let mut props = HashMap::new();
if let Ok(java_home) = std::env::var("JAVA_HOME") {
props.insert("java.home".to_string(), java_home);
}
props.insert("os.name".to_string(), std::env::consts::OS.to_string());
props.insert("os.arch".to_string(), std::env::consts::ARCH.to_string());
props.insert("os.version".to_string(), Self::get_os_version());
for (key, value) in std::env::vars() {
props.insert(format!("env.{key}"), value);
}
props
}
fn get_os_version() -> String {
#[cfg(target_os = "macos")]
{
use std::process::Command;
if let Ok(output) = Command::new("sw_vers").arg("-productVersion").output() {
if let Ok(version) = String::from_utf8(output.stdout) {
return version.trim().to_string();
}
}
}
"unknown".to_string()
}
}
impl Default for ProfileActivationContext {
fn default() -> Self {
Self::new()
}
}
pub struct ProfileActivator;
impl ProfileActivator {
pub fn is_active(profile: &Profile, context: &ProfileActivationContext) -> bool {
if context.inactive_profile_ids.contains(&profile.id) {
return false;
}
if context.active_profile_ids.contains(&profile.id) {
return true;
}
if let Some(ref activation) = profile.activation {
Self::check_activation(activation, context)
} else {
false
}
}
fn check_activation(activation: &Activation, context: &ProfileActivationContext) -> bool {
if activation.active_by_default == Some(true) {
return true;
}
if let Some(ref jdk) = activation.jdk {
if !Self::check_jdk_version(jdk) {
return false;
}
}
if let Some(ref os) = activation.os {
if !Self::check_os(os) {
return false;
}
}
if let Some(ref property) = activation.property {
if !Self::check_property(property, context) {
return false;
}
}
if let Some(ref file) = activation.file {
if !Self::check_file(file, &context.project_directory) {
return false;
}
}
true
}
fn check_jdk_version(jdk_spec: &str) -> bool {
if let Ok(java_version) = std::env::var("JAVA_VERSION") {
return jdk_spec == java_version || java_version.starts_with(jdk_spec);
}
use std::process::Command;
if let Ok(output) = Command::new("java").arg("-version").output() {
let stderr = String::from_utf8_lossy(&output.stderr);
return stderr.contains(jdk_spec);
}
false
}
fn check_os(os: &ActivationOS) -> bool {
if let Some(ref name) = os.name {
if std::env::consts::OS != name {
return false;
}
}
if let Some(ref family) = os.family {
let current_family = match std::env::consts::OS {
"windows" => "windows",
"macos" => "mac",
"linux" => "unix",
_ => "unknown",
};
if current_family != family {
return false;
}
}
if let Some(ref arch) = os.arch {
if std::env::consts::ARCH != arch {
return false;
}
}
true
}
fn check_property(property: &ActivationProperty, context: &ProfileActivationContext) -> bool {
if let Some(ref name) = property.name {
let value = context.user_properties.get(name)
.or_else(|| context.system_properties.get(name))
.or_else(|| context.project_properties.get(name))
.map(|s| s.as_str());
if let Some(ref expected_value) = property.value {
return value == Some(expected_value.as_str());
} else {
return value.is_some();
}
}
false
}
fn check_file(file: &ActivationFile, project_dir: &Option<PathBuf>) -> bool {
if let Some(dir) = project_dir {
if let Some(missing) = &file.missing {
let path = dir.join(missing);
return !path.exists();
}
if let Some(exists) = &file.exists {
let path = dir.join(exists);
return path.exists();
}
}
false
}
pub fn get_active_profiles(
profiles: &[Profile],
context: &ProfileActivationContext,
) -> Vec<Profile> {
profiles
.iter()
.filter(|p| Self::is_active(p, context))
.cloned()
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::model::profile::{Profile, Activation};
fn create_test_profile(id: &str) -> Profile {
Profile {
id: id.to_string(),
activation: None,
build: None,
dependencies: None,
dependency_management: None,
properties: None,
repositories: None,
plugin_repositories: None,
}
}
#[test]
fn test_profile_explicit_activation() {
let profile = create_test_profile("test-profile");
let mut context = ProfileActivationContext::new();
context.active_profile_ids = vec!["test-profile".to_string()];
assert!(ProfileActivator::is_active(&profile, &context));
}
#[test]
fn test_profile_explicit_deactivation() {
let profile = create_test_profile("test-profile");
let mut context = ProfileActivationContext::new();
context.inactive_profile_ids = vec!["test-profile".to_string()];
assert!(!ProfileActivator::is_active(&profile, &context));
}
#[test]
fn test_profile_active_by_default() {
let mut profile = create_test_profile("test-profile");
profile.activation = Some(Activation {
active_by_default: Some(true),
jdk: None,
os: None,
property: None,
file: None,
});
let context = ProfileActivationContext::new();
assert!(ProfileActivator::is_active(&profile, &context));
}
#[test]
fn test_profile_property_activation() {
let mut profile = create_test_profile("test-profile");
profile.activation = Some(Activation {
active_by_default: None,
jdk: None,
os: None,
property: Some(ActivationProperty {
name: Some("test.property".to_string()),
value: Some("test-value".to_string()),
}),
file: None,
});
let mut context = ProfileActivationContext::new();
let mut props = HashMap::new();
props.insert("test.property".to_string(), "test-value".to_string());
context.user_properties = props;
assert!(ProfileActivator::is_active(&profile, &context));
}
#[test]
fn test_profile_property_activation_mismatch() {
let mut profile = create_test_profile("test-profile");
profile.activation = Some(Activation {
active_by_default: None,
jdk: None,
os: None,
property: Some(ActivationProperty {
name: Some("test.property".to_string()),
value: Some("test-value".to_string()),
}),
file: None,
});
let mut context = ProfileActivationContext::new();
let mut props = HashMap::new();
props.insert("test.property".to_string(), "wrong-value".to_string());
context.user_properties = props;
assert!(!ProfileActivator::is_active(&profile, &context));
}
#[test]
fn test_profile_os_activation() {
let mut profile = create_test_profile("test-profile");
profile.activation = Some(Activation {
active_by_default: None,
jdk: None,
os: Some(ActivationOS {
name: Some(std::env::consts::OS.to_string()),
family: None,
arch: None,
version: None,
}),
property: None,
file: None,
});
let context = ProfileActivationContext::new();
assert!(ProfileActivator::is_active(&profile, &context));
}
#[test]
fn test_get_active_profiles() {
let profiles = vec![
create_test_profile("profile1"),
create_test_profile("profile2"),
create_test_profile("profile3"),
];
let mut context = ProfileActivationContext::new();
context.active_profile_ids = vec!["profile1".to_string(), "profile3".to_string()];
let active = ProfileActivator::get_active_profiles(&profiles, &context);
assert_eq!(active.len(), 2);
assert!(active.iter().any(|p| p.id == "profile1"));
assert!(active.iter().any(|p| p.id == "profile3"));
}
}