Skip to content

Commit 69ef0f4

Browse files
committed
Refactor Java options and paths to use WidePathBuf for improved path handling
Assisted-by: codex:gpt-5.4
1 parent 924d296 commit 69ef0f4

5 files changed

Lines changed: 141 additions & 60 deletions

File tree

src/java.rs

Lines changed: 19 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ use crate::wide::{
2222
WideDisplay, WideString, is_dot_or_dot_dot, trim_wide_whitespace, wide_contains,
2323
wide_slice_from_ptr,
2424
};
25+
use crate::wide_path::WidePathBuf;
2526

2627
/// Collect the filesystem and JVM options needed to launch HMCL.
2728
pub struct JavaOptions {
28-
pub workdir: WideString,
29-
pub jar_path: WideString,
29+
pub workdir: WidePathBuf,
30+
pub jar_path: WidePathBuf,
3031
pub jvm_options: Option<WideString>,
3132
}
3233

@@ -46,7 +47,7 @@ impl JavaVersion {
4647
}
4748

4849
/// Read the PE version resource from a Java executable.
49-
pub fn from_executable(path: &WideString) -> Self {
50+
pub fn from_executable(path: &WidePathBuf) -> Self {
5051
// Match the C++ launcher and read the executable's version resource
5152
// instead of spawning `java -version`.
5253
let size = unsafe { GetFileVersionInfoSizeW(path.as_pcwstr(), ptr::null_mut()) };
@@ -65,9 +66,8 @@ impl JavaVersion {
6566
// version components in the same format as the upstream launcher.
6667
let mut info_ptr = ptr::null_mut();
6768
let mut info_len = 0u32;
68-
let result = unsafe {
69-
VerQueryValueW(data.as_ptr().cast(), w!("\\"), &mut info_ptr, &mut info_len)
70-
};
69+
let result =
70+
unsafe { VerQueryValueW(data.as_ptr().cast(), w!("\\"), &mut info_ptr, &mut info_len) };
7171
if result == 0 || info_ptr.is_null() || info_len < size_of::<VS_FIXEDFILEINFO>() as u32 {
7272
return Self::invalid();
7373
}
@@ -141,7 +141,7 @@ impl Display for JavaVersion {
141141
/// Pair a Java executable path with the version discovered from its metadata.
142142
pub struct JavaRuntime {
143143
pub version: JavaVersion,
144-
pub executable_path: WideString,
144+
pub executable_path: WidePathBuf,
145145
}
146146

147147
/// Own the set of discovered Java runtimes before launch selection.
@@ -158,7 +158,7 @@ impl JavaList {
158158
}
159159

160160
/// Add one candidate runtime when it exists, is unique, and is new enough.
161-
pub fn try_add(&mut self, java_executable: WideString) -> bool {
161+
pub fn try_add(&mut self, java_executable: WidePathBuf) -> bool {
162162
if !is_regular_file(&java_executable) {
163163
return false;
164164
}
@@ -202,12 +202,13 @@ impl JavaList {
202202
/// Sort runtimes from lowest to highest version so callers can try the best
203203
/// match last-to-first.
204204
pub fn sort_by_version(&mut self) {
205-
self.runtimes.sort_by(|left, right| left.version.cmp(&right.version));
205+
self.runtimes
206+
.sort_by(|left, right| left.version.cmp(&right.version));
206207
}
207208
}
208209

209210
/// Launch HMCL with a specific Java executable.
210-
pub fn launch_jvm(java_executable_path: &WideString, options: &JavaOptions) -> bool {
211+
pub fn launch_jvm(java_executable_path: &WidePathBuf, options: &JavaOptions) -> bool {
211212
let mut command = WideString::new();
212213
if !command.push_char('"')
213214
|| !command.push_slice(java_executable_path.as_slice())
@@ -279,7 +280,11 @@ pub fn launch_jvm(java_executable_path: &WideString, options: &JavaOptions) -> b
279280
}
280281

281282
/// Search one directory for subdirectories that look like Java homes.
282-
pub fn search_java_in_dir(result: &mut JavaList, basedir: &WideString, java_executable_name: &str) {
283+
pub fn search_java_in_dir(
284+
result: &mut JavaList,
285+
basedir: &WidePathBuf,
286+
java_executable_name: &str,
287+
) {
283288
log_verbose_fmt(format_args!(
284289
"Searching in directory: {}",
285290
WideDisplay(basedir.as_slice())
@@ -325,7 +330,7 @@ pub fn search_java_in_dir(result: &mut JavaList, basedir: &WideString, java_exec
325330
/// Probe the common vendor directories under Program Files.
326331
pub fn search_java_in_program_files(
327332
result: &mut JavaList,
328-
program_files: &WideString,
333+
program_files: &WidePathBuf,
329334
java_executable_name: &str,
330335
) {
331336
// Search the same vendor folders as the upstream launcher.
@@ -441,7 +446,7 @@ pub fn search_java_in_registry(result: &mut JavaList, sub_key: PCWSTR, java_exec
441446
units -= 1;
442447
}
443448

444-
if let Some(mut executable) = WideString::from_utf16(&java_home[..units]) {
449+
if let Some(mut executable) = WidePathBuf::from_utf16(&java_home[..units]) {
445450
if executable.push_path_component_str("bin")
446451
&& executable.push_path_component_str(java_executable_name)
447452
{
@@ -469,7 +474,7 @@ pub fn search_java_in_path(result: &mut JavaList, path: &[u16], java_executable_
469474

470475
let entry = trim_wide_whitespace(&path[start..end]);
471476
if !entry.is_empty() {
472-
if let Some(mut java_executable) = WideString::from_utf16(entry) {
477+
if let Some(mut java_executable) = WidePathBuf::from_utf16(entry) {
473478
if java_executable.push_path_component_str(java_executable_name) {
474479
if wide_contains(java_executable.as_slice(), oracle_java) {
475480
// Keep the upstream exclusion for Oracle's shared shim

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ mod java;
99
mod launcher;
1010
pub mod platform;
1111
mod wide;
12+
mod wide_path;
1213

1314
/// The minimum required Java major version.
1415
pub(crate) const HMCL_EXPECTED_JAVA_MAJOR_VERSION: u16 = 17;

src/platform.rs

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,13 @@ use windows_sys::Win32::System::Threading::GetCurrentProcess;
1919
use windows_sys::core::{PCWSTR, s, w};
2020

2121
use crate::wide::WideString;
22+
use crate::wide_path::WidePathBuf;
2223

2324
/// Hold the launcher's working directory and the file name passed to
2425
/// `java -jar`.
2526
pub struct SelfPath {
26-
pub workdir: WideString,
27-
pub jar_path: WideString,
27+
pub workdir: WidePathBuf,
28+
pub jar_path: WidePathBuf,
2829
}
2930

3031
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
@@ -159,8 +160,8 @@ pub fn get_self_path() -> Option<SelfPath> {
159160
}
160161

161162
Some(SelfPath {
162-
workdir: WideString::from_utf16(&path[..slash])?,
163-
jar_path: WideString::from_utf16(&path[slash + 1..])?,
163+
workdir: WidePathBuf::from_utf16(&path[..slash])?,
164+
jar_path: WidePathBuf::from_utf16(&path[slash + 1..])?,
164165
})
165166
}
166167

@@ -203,12 +204,12 @@ pub fn get_env_var(name: PCWSTR) -> Option<WideString> {
203204
}
204205

205206
/// Read an environment variable that represents a filesystem path.
206-
pub fn get_env_path(name: PCWSTR) -> Option<WideString> {
207-
get_env_var(name)
207+
pub fn get_env_path(name: PCWSTR) -> Option<WidePathBuf> {
208+
Some(WidePathBuf::from_wide_string(get_env_var(name)?))
208209
}
209210

210211
/// Treat only ordinary filesystem files as valid launcher targets.
211-
pub fn is_regular_file(path: &WideString) -> bool {
212+
pub fn is_regular_file(path: &WidePathBuf) -> bool {
212213
let attributes = unsafe { GetFileAttributesW(path.as_pcwstr()) };
213214
attributes != INVALID_FILE_ATTRIBUTES
214215
&& (attributes & FILE_ATTRIBUTE_DIRECTORY) == 0

src/wide.rs

Lines changed: 1 addition & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -183,36 +183,6 @@ impl WideString {
183183
self.data[self.len] = 0;
184184
true
185185
}
186-
187-
/// Append `\` when the current path does not already end with a separator.
188-
pub fn push_path_separator(&mut self) -> bool {
189-
if self.is_empty() {
190-
return true;
191-
}
192-
193-
let last = self.as_slice()[self.len - 1];
194-
if last == b'\\' as u16 || last == b'/' as u16 {
195-
true
196-
} else {
197-
self.push_char('\\')
198-
}
199-
}
200-
201-
/// Append one UTF-16 path component, inserting a separator if required.
202-
pub fn push_path_component(&mut self, value: &[u16]) -> bool {
203-
if value.is_empty() {
204-
return true;
205-
}
206-
self.push_path_separator() && self.push_slice(value)
207-
}
208-
209-
/// Append one UTF-8 path component, inserting a separator if required.
210-
pub fn push_path_component_str(&mut self, value: &str) -> bool {
211-
if value.is_empty() {
212-
return true;
213-
}
214-
self.push_path_separator() && self.push_str(value)
215-
}
216186
}
217187

218188
impl Write for WideString {
@@ -311,7 +281,7 @@ fn wide_is_whitespace(value: u16) -> bool {
311281

312282
#[cfg(test)]
313283
mod tests {
314-
use super::{WideString, trim_wide_whitespace, wide_contains};
284+
use super::{WideString, trim_wide_whitespace};
315285

316286
#[test]
317287
/// Trim surrounding whitespace while keeping the inner contents unchanged.
@@ -323,12 +293,4 @@ mod tests {
323293
"hello".encode_utf16().collect::<std::vec::Vec<_>>()
324294
);
325295
}
326-
327-
#[test]
328-
/// Find a path fragment inside a UTF-16 path string.
329-
fn substring_search() {
330-
let value = WideString::from_str("C:\\Common Files\\Oracle\\Java\\bin").unwrap();
331-
let needle = WideString::from_str("\\Common Files\\Oracle\\Java\\").unwrap();
332-
assert!(wide_contains(value.as_slice(), needle.as_slice()));
333-
}
334296
}

src/wide_path.rs

Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
use windows_sys::core::PCWSTR;
2+
3+
use crate::wide::WideString;
4+
5+
/// Owned UTF-16 storage for filesystem paths.
6+
pub struct WidePathBuf {
7+
inner: WideString,
8+
}
9+
10+
impl WidePathBuf {
11+
/// Create an empty path buffer with no heap allocation.
12+
pub const fn new() -> Self {
13+
Self {
14+
inner: WideString::new(),
15+
}
16+
}
17+
18+
/// Build a UTF-16 path by encoding UTF-8 input.
19+
pub fn from_str(value: &str) -> Option<Self> {
20+
Some(Self {
21+
inner: WideString::from_str(value)?,
22+
})
23+
}
24+
25+
/// Copy an existing UTF-16 path into owned storage.
26+
pub fn from_utf16(value: &[u16]) -> Option<Self> {
27+
Some(Self {
28+
inner: WideString::from_utf16(value)?,
29+
})
30+
}
31+
32+
/// Wrap an existing UTF-16 string whose contents represent a path.
33+
pub fn from_wide_string(value: WideString) -> Self {
34+
Self { inner: value }
35+
}
36+
37+
/// Report whether the path is empty.
38+
pub fn is_empty(&self) -> bool {
39+
self.inner.is_empty()
40+
}
41+
42+
/// Borrow the initialized UTF-16 contents without the trailing terminator.
43+
pub fn as_slice(&self) -> &[u16] {
44+
self.inner.as_slice()
45+
}
46+
47+
/// Expose the path buffer as a Win32 `PCWSTR`.
48+
pub fn as_pcwstr(&self) -> PCWSTR {
49+
self.inner.as_pcwstr()
50+
}
51+
52+
/// Clone the path into a second heap-backed buffer.
53+
pub fn try_clone(&self) -> Option<Self> {
54+
Some(Self {
55+
inner: self.inner.try_clone()?,
56+
})
57+
}
58+
59+
/// Append `\` when the current path does not already end with a separator.
60+
pub fn push_path_separator(&mut self) -> bool {
61+
let Some(&last) = self.inner.as_slice().last() else {
62+
return true;
63+
};
64+
65+
if last == b'\\' as u16 || last == b'/' as u16 {
66+
true
67+
} else {
68+
self.inner.push_char('\\')
69+
}
70+
}
71+
72+
/// Append one UTF-16 path component, inserting a separator if required.
73+
pub fn push_path_component(&mut self, value: &[u16]) -> bool {
74+
if value.is_empty() {
75+
return true;
76+
}
77+
self.push_path_separator() && self.inner.push_slice(value)
78+
}
79+
80+
/// Append one UTF-8 path component, inserting a separator if required.
81+
pub fn push_path_component_str(&mut self, value: &str) -> bool {
82+
if value.is_empty() {
83+
return true;
84+
}
85+
self.push_path_separator() && self.inner.push_str(value)
86+
}
87+
}
88+
89+
#[cfg(test)]
90+
mod tests {
91+
use super::WidePathBuf;
92+
use crate::wide::wide_contains;
93+
94+
/// Find a path fragment inside a UTF-16 path string.
95+
#[test]
96+
fn substring_search() {
97+
let value = WidePathBuf::from_str("C:\\Common Files\\Oracle\\Java\\bin").unwrap();
98+
let needle = WidePathBuf::from_str("\\Common Files\\Oracle\\Java\\").unwrap();
99+
assert!(wide_contains(value.as_slice(), needle.as_slice()));
100+
}
101+
102+
/// Join path components without duplicating separators.
103+
#[test]
104+
fn push_path_component() {
105+
let mut value = WidePathBuf::from_str("C:\\Java").unwrap();
106+
assert!(value.push_path_component_str("bin"));
107+
assert_eq!(
108+
value.as_slice(),
109+
"C:\\Java\\bin".encode_utf16().collect::<std::vec::Vec<_>>()
110+
);
111+
}
112+
}

0 commit comments

Comments
 (0)