1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
//! Java 的搜索,版本检测
use std::path::{Path, PathBuf};

use inner_future::stream::StreamExt;

use crate::{
    prelude::*,
    utils::{locate_path, Arch},
};

/// 一个 Java 运行时类型
#[derive(Debug, Clone)]
pub struct JavaRuntime {
    java_path: String,
    java_version: String,
    java_main_version: u8,
    java_64bit: bool,
    java_arch: Arch,
}

impl JavaRuntime {
    /// 通过一个指向 Java 可执行文件的路径来创建 [`JavaRuntime`]
    ///
    /// 在此会尝试运行这个文件并获取相关的版本信息,确认无误后返回
    pub async fn from_java_path(java_path: impl AsRef<std::ffi::OsStr>) -> DynResult<Self> {
        tracing::debug!(
            "正在重新定位 Java 执行文件: {}",
            java_path.as_ref().to_string_lossy()
        );
        let java_path = locate_path(java_path.as_ref());
        tracing::debug!("Java 执行文件已重新定位: {}", java_path.display());
        let output = query_java_version_output(&java_path).await?;
        let version = query_java_version(&output);
        let java_main_version = get_java_version(version);
        let java_64bit = query_java_is_64bit(&output);
        let java_arch = crate::utils::get_exec_arch(&java_path).await?;
        Ok(Self {
            java_path: java_path.to_string_lossy().to_string(),
            java_64bit,
            java_version: version.to_owned(),
            java_main_version,
            java_arch,
        })
    }

    /// 获取此 Java 运行时的可执行文件路径
    #[inline]
    pub fn path(&self) -> &str {
        &self.java_path
    }

    /// 获取此 Java 运行时的版本号
    #[inline]
    pub fn version(&self) -> &str {
        &self.java_version
    }

    /// 获取此 Java 运行时的运行架构是否是针对 64 位平台的
    #[inline]
    pub fn is_64bit(&self) -> bool {
        self.java_64bit
    }

    /// 获取此 Java 运行时的主 Java 版本号
    #[inline]
    pub fn main_version(&self) -> u8 {
        self.java_main_version
    }

    /// 获取此 Java 运行时的运行架构
    #[inline]
    pub fn arch(&self) -> Arch {
        self.java_arch
    }
}

/// 执行 Java 并使用 -version 获取其版本输出
async fn query_java_version_output(java_path: impl AsRef<std::ffi::OsStr>) -> DynResult<String> {
    let c = {
        #[cfg(windows)]
        {
            use inner_future::process::windows::CommandExt;
            inner_future::process::Command::new(java_path)
                .arg("-version")
                .stdout(std::process::Stdio::piped())
                .stderr(std::process::Stdio::piped())
                .creation_flags(0x00000200 | 0x08000000)
                .spawn()?
        }
        #[cfg(not(windows))]
        {
            inner_future::process::Command::new(java_path)
                .arg("-version")
                .stdout(std::process::Stdio::piped())
                .stderr(std::process::Stdio::piped())
                .spawn()?
        }
    };
    let output = c.output().await?;
    Ok(String::from_utf8(output.stderr)?)
}

/// 根据 Java 的版本输出确认是否为 64 位版本
fn query_java_is_64bit(java_output: &str) -> bool {
    java_output.contains("64-Bit")
}

/// 根据 Java 裁剪的版本号文本确认主版本号
/// - `1.8.x` 将返回 `8`
/// - `1.7.x` 将返回 `7`
/// - `10+` 将返回其主版本数字
/// - 其余返回 `0` 表示未知版本
fn get_java_version(java_version_string: &str) -> u8 {
    fn parser(input: &str) -> nom::IResult<&str, &str> {
        nom::character::complete::digit1(input)
    }
    if let Ok((_, r)) = parser(java_version_string) {
        let r = r.parse().unwrap(); // 应当不会出错的
        if r > 1 {
            r
        } else if java_version_string.contains("1.8") {
            8
        } else if java_version_string.contains("1.7") {
            7
        } else {
            0 // 未知版本
        }
    } else if java_version_string.contains("1.8") {
        8
    } else if java_version_string.contains("1.7") {
        7
    } else {
        0 // 未知版本
    }
}

/// 搜索可能存在 Java 的地方找到可用的 Java 运行时
/// 返回的结果列表项均为 java.exe/javaw.exe 或 java 执行文件的目录
pub async fn search_for_java() -> Vec<String> {
    // 从安装目录中搜索 Java
    async fn check_bin_java_directory(path: impl AsRef<Path>, result: &mut Vec<String>) {
        if let Ok(mut d) = inner_future::fs::read_dir(path).await {
            while let Ok(Some(d)) = d.try_next().await {
                let mut path = d.path();
                #[cfg(target_os = "macos")]
                path.push("Contents/Home");
                path.push("bin");
                #[cfg(target_os = "windows")]
                {
                    path.push("java.exe");
                    if path.is_file() {
                        tracing::debug!("Added {}", path.display());
                        result.push(d.path().to_string_lossy().to_string());
                    }
                    {
                        path.pop();
                        path.push("javaw.exe");
                        if path.is_file() {
                            tracing::debug!("Added {}", path.display());
                            result.push(path.to_string_lossy().to_string());
                        }
                    }
                }
                #[cfg(not(windows))]
                {
                    path.push("java");
                    if path.is_file() {
                        result.push(path.to_string_lossy().to_string());
                    }
                }
            }
        }
    }

    fn check_java(path: impl Into<PathBuf>, result: &mut Vec<String>) {
        let mut path: PathBuf = path.into();
        #[cfg(windows)]
        {
            path.push("java.exe");
            if path.is_file() {
                result.push(path.to_string_lossy().to_string());
            }
            path.pop();
            path.push("javaw.exe");
            if path.is_file() {
                result.push(path.to_string_lossy().to_string());
            }
        }
        #[cfg(not(windows))]
        {
            path.push("java");
            if path.is_file() {
                result.push(path.to_string_lossy().to_string());
            }
        }
    }
    #[cfg(windows)]
    {
        let mut result = Vec::with_capacity(16);
        // 从注册表中搜索 JavaHome
        use winreg::{enums::*, *};
        fn search_local_machine_reg_value(key: &str, result: &mut Vec<String>) {
            let hklm = RegKey::predef(HKEY_LOCAL_MACHINE);
            if let Ok(subkey) = hklm
                .open_subkey("SOFTWARE\\JavaSoft")
                .and_then(|subkey| subkey.open_subkey(key))
            {
                for s in subkey.enum_keys().flatten() {
                    let subkey = subkey.open_subkey(s);
                    if let Ok(subkey) = subkey {
                        if let Ok(v) = subkey.get_value("JavaHome") {
                            result.push(v);
                        }
                    }
                }
            }
        }
        search_local_machine_reg_value("Java Runtime Environment", &mut result);
        search_local_machine_reg_value("Java Development Kit", &mut result);
        search_local_machine_reg_value("JRE", &mut result);
        search_local_machine_reg_value("JDK", &mut result);
        async fn list_java_directory(path: &str, result: &mut Vec<String>) {
            let mut path = std::path::PathBuf::from(path);
            path.push("Java");
            check_bin_java_directory(&path, result).await;
            path.pop();
            path.push("BellSoft");
            check_bin_java_directory(&path, result).await;
            path.pop();
            path.push("AdoptOpenJDK");
            check_bin_java_directory(&path, result).await;
            path.pop();
            path.push("Zulu");
            check_bin_java_directory(&path, result).await;
            path.pop();
            path.push("Microsoft");
            check_bin_java_directory(&path, result).await;
            path.pop();
            path.push("Eclipse Foundation");
            check_bin_java_directory(&path, result).await;
            path.pop();
            path.push("Semeru");
            check_bin_java_directory(&path, result).await;
            path.pop();
        }
        if let Ok(program_files) = std::env::var("ProgramFiles")
            .or_else::<std::env::VarError, _>(|_| Ok("C:\\Program Files".into()))
        {
            list_java_directory(&program_files, &mut result).await;
        }
        if let Ok(program_files) = std::env::var("ProgramFiles(x86)")
            .or_else::<std::env::VarError, _>(|_| Ok("C:\\Program Files (x86)".into()))
        {
            list_java_directory(&program_files, &mut result).await;
            let mut minecraft_launcher_dir = PathBuf::from(program_files);
            minecraft_launcher_dir
                .push("Minecraft Launcher\\runtime\\jre-legacy\\windows-x64\\jre-legacy\\bin");
            check_java(&minecraft_launcher_dir, &mut result);
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.push("windows-x86\\jre-legacy\\bin");
            check_java(&minecraft_launcher_dir, &mut result);
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.push("java-runtime-alpha\\windows-x64\\java-runtime-alpha\\bin");
            check_java(&minecraft_launcher_dir, &mut result);
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.pop();
            minecraft_launcher_dir.push("windows-x86\\java-runtime-alpha\\bin");
            check_java(&minecraft_launcher_dir, &mut result);
        }
        if let Ok(program_files) = std::env::var("ProgramFiles(ARM)")
            .or_else::<std::env::VarError, _>(|_| Ok("C:\\Program Files (ARM)".into()))
        {
            list_java_directory(&program_files, &mut result).await;
        }
        // 从环境变量中查询 java.exe
        if let Ok(paths) = std::env::var("PATH") {
            for path in paths.split(';') {
                check_java(path, &mut result);
            }
        }
        // 从 JABBA 中查询 java.exe
        if let Ok(path) = std::env::var("JABBA_HOME") {
            let path = PathBuf::from(path).join("jdk");
            check_bin_java_directory(path.as_path(), &mut result).await;
        }
        let mut result: Vec<_> = result
            .into_iter()
            .map(|mut a| {
                #[cfg(windows)]
                {
                    if !a.ends_with("java.exe")
                        && !a.ends_with("javaw.exe")
                        && !a.ends_with("java")
                        && !a.ends_with("javaw")
                    {
                        if !a.ends_with('\\') && !a.ends_with('/') {
                            a.push('\\');
                        }
                        a.push_str("bin\\java.exe");
                    }
                }
                #[cfg(not(windows))]
                {
                    if !a.ends_with("java") {
                        if !a.ends_with('/') {
                            a.push('/');
                        }
                        a.push_str("bin/java");
                    }
                }
                a
            })
            .collect();
        result.sort();
        result.dedup();
        result
    }
    #[cfg(target_os = "linux")]
    {
        let mut result = Vec::with_capacity(16);

        // 一些常见目录
        check_bin_java_directory("/usr/java/bin", &mut result).await;
        check_bin_java_directory("/usr/lib/jvm/bin", &mut result).await;
        check_bin_java_directory("/usr/lib32/jvm/bin", &mut result).await;

        // 从环境变量中查询 java.exe
        if let Ok(paths) = std::env::var("PATH") {
            for path in paths.split(';') {
                check_java(path, &mut result);
            }
        }
        // 从 JABBA 中查询 java.exe
        if let Ok(path) = std::env::var("JABBA_HOME") {
            let path = PathBuf::from(path).join("jdk");
            check_bin_java_directory(path.as_path(), &mut result).await;
        }
        // 主目录里的 .minecraft/runtime 文件夹
        if let Some(mut home_path) = dirs::home_dir() {
            home_path.push(".minecraft/runtime/jre-legacy/linux/jre-legacy/bin");
            check_java(&home_path, &mut result);
            home_path.push("../../../../java-runtime-alpha/linux/java-runtime-alpha/bin");
            check_java(&home_path, &mut result);
        }

        result
    }
    #[cfg(target_os = "macos")]
    {
        let mut result = Vec::with_capacity(16);

        check_bin_java_directory("/Library/Java/JavaVirtualMachines", &mut result).await;
        check_bin_java_directory("/System/Library/Java/JavaVirtualMachines", &mut result).await;
        check_java(
            "/Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home/bin/java",
            &mut result,
        );
        check_java("/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/MacOS/itms/java/bin/java", &mut result);

        result
    }
}

/// 根据 Java 的输出裁剪出版本号文本
fn query_java_version(java_output: &str) -> &str {
    fn parser(input: &str) -> nom::IResult<&str, &str> {
        nom::bytes::complete::take_until("\"")(input)
    }
    let pat = "version \"";
    if let Some(p) = java_output.find(pat) {
        if let Ok((_, r)) = parser(&java_output[p + pat.len()..]) {
            r
        } else {
            ""
        }
    } else {
        ""
    }
}