Skip to content

Commit 3296139

Browse files
authored
Add CLI classpath options (#184)
* Add CLI classpath options * Simplify URL classpath lookup * Fix platform classpath handling * Use File path separator for class loading * Separate RustJar class loading
1 parent fd629c7 commit 3296139

19 files changed

Lines changed: 627 additions & 84 deletions

File tree

java_runtime/src/classes/java/io/file.rs

Lines changed: 56 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use alloc::vec;
22

33
use java_class_proto::{JavaFieldProto, JavaMethodProto};
4-
use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString};
4+
use java_constants::{FieldAccessFlags, MethodAccessFlags};
5+
use jvm::{ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString};
56

67
use crate::{FileType, RuntimeClassProto, RuntimeContext, classes::java::lang::String};
78

@@ -15,6 +16,7 @@ impl File {
1516
parent_class: Some("java/lang/Object"),
1617
interfaces: vec![],
1718
methods: vec![
19+
JavaMethodProto::new("<clinit>", "()V", Self::clinit, MethodAccessFlags::STATIC),
1820
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init, Default::default()),
1921
JavaMethodProto::new("getPath", "()Ljava/lang/String;", Self::get_path, Default::default()),
2022
JavaMethodProto::new("exists", "()Z", Self::exists, Default::default()),
@@ -23,11 +25,63 @@ impl File {
2325
JavaMethodProto::new("delete", "()Z", Self::delete, Default::default()),
2426
JavaMethodProto::new("length", "()J", Self::length, Default::default()),
2527
],
26-
fields: vec![JavaFieldProto::new("path", "Ljava/lang/String;", Default::default())],
28+
fields: vec![
29+
JavaFieldProto::new(
30+
"separatorChar",
31+
"C",
32+
FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL,
33+
),
34+
JavaFieldProto::new(
35+
"separator",
36+
"Ljava/lang/String;",
37+
FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL,
38+
),
39+
JavaFieldProto::new(
40+
"pathSeparatorChar",
41+
"C",
42+
FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL,
43+
),
44+
JavaFieldProto::new(
45+
"pathSeparator",
46+
"Ljava/lang/String;",
47+
FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL,
48+
),
49+
JavaFieldProto::new("path", "Ljava/lang/String;", Default::default()),
50+
],
2751
access_flags: Default::default(),
2852
}
2953
}
3054

55+
async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> {
56+
tracing::debug!("java.io.File::<clinit>()");
57+
58+
let separator_char = if cfg!(windows) { '\\' } else { '/' };
59+
let separator = if cfg!(windows) { "\\" } else { "/" };
60+
let path_separator_char = if cfg!(windows) { ';' } else { ':' };
61+
let path_separator = if cfg!(windows) { ";" } else { ":" };
62+
63+
jvm.put_static_field("java/io/File", "separatorChar", "C", separator_char as JavaChar)
64+
.await?;
65+
jvm.put_static_field(
66+
"java/io/File",
67+
"separator",
68+
"Ljava/lang/String;",
69+
JavaLangString::from_rust_string(jvm, separator).await?,
70+
)
71+
.await?;
72+
jvm.put_static_field("java/io/File", "pathSeparatorChar", "C", path_separator_char as JavaChar)
73+
.await?;
74+
jvm.put_static_field(
75+
"java/io/File",
76+
"pathSeparator",
77+
"Ljava/lang/String;",
78+
JavaLangString::from_rust_string(jvm, path_separator).await?,
79+
)
80+
.await?;
81+
82+
Ok(())
83+
}
84+
3185
async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>, pathname: ClassInstanceRef<String>) -> Result<()> {
3286
tracing::debug!("java.io.File::<init>({this:?}, {pathname:?})");
3387

java_runtime/src/classes/java/lang/class_loader.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -97,28 +97,45 @@ impl ClassLoader {
9797
)
9898
.await?;
9999

100-
let url_array = if !class_path.is_null() {
100+
let (class_paths, urls) = if !class_path.is_null() {
101101
let class_path = JavaLangString::to_rust_string(jvm, &class_path).await?;
102+
let path_separator: ClassInstanceRef<String> = jvm.get_static_field("java/io/File", "pathSeparator", "Ljava/lang/String;").await?;
103+
let path_separator = JavaLangString::to_rust_string(jvm, &path_separator).await?;
102104

105+
let mut class_paths = Vec::new();
103106
let mut urls = Vec::new();
104-
for path in class_path.split(':') {
105-
// TODO File.pathSeparator
107+
for path in class_path.split(path_separator.as_str()) {
108+
class_paths.push(JavaLangString::from_rust_string(jvm, path).await?);
109+
106110
let path = JavaLangString::from_rust_string(jvm, &format!("file:{path}")).await?;
107111
let url = jvm.new_class("java/net/URL", "(Ljava/lang/String;)V", (path,)).await?;
108-
109112
urls.push(url);
110113
}
111114

112-
let mut url_array = jvm.instantiate_array("Ljava/net/URL;", urls.len()).await?;
113-
jvm.store_array(&mut url_array, 0, urls).await?;
114-
115-
url_array
115+
(class_paths, urls)
116116
} else {
117-
jvm.instantiate_array("Ljava/net/URL;", 0).await?
117+
(Vec::new(), Vec::new())
118118
};
119119

120+
let mut class_path_array = jvm.instantiate_array("Ljava/lang/String;", class_paths.len()).await?;
121+
jvm.store_array(&mut class_path_array, 0, class_paths).await?;
122+
let rustjar_class_loader = jvm
123+
.new_class(
124+
"org/rustjava/lang/RustJarClassLoader",
125+
"([Ljava/lang/String;Ljava/lang/ClassLoader;)V",
126+
(class_path_array, None),
127+
)
128+
.await?;
129+
130+
let mut url_array = jvm.instantiate_array("Ljava/net/URL;", urls.len()).await?;
131+
jvm.store_array(&mut url_array, 0, urls).await?;
132+
120133
let url_class_loader = jvm
121-
.new_class("java/net/URLClassLoader", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V", (url_array, None))
134+
.new_class(
135+
"java/net/URLClassLoader",
136+
"([Ljava/net/URL;Ljava/lang/ClassLoader;)V",
137+
(url_array, rustjar_class_loader),
138+
)
122139
.await?;
123140

124141
let class_loader_type: ClassInstanceRef<String> = jvm

java_runtime/src/classes/java/net/url_class_loader.rs

Lines changed: 11 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ use jvm::{
77
};
88

99
use crate::{
10-
RuntimeClassProto, RuntimeContext,
10+
FileType, RuntimeClassProto, RuntimeContext,
1111
classes::java::{
1212
lang::{Class, ClassLoader, String},
1313
net::{JarURLConnection, URL},
@@ -59,32 +59,14 @@ impl URLClassLoader {
5959

6060
async fn find_class(
6161
jvm: &Jvm,
62-
context: &mut RuntimeContext,
62+
_: &mut RuntimeContext,
6363
this: ClassInstanceRef<Self>,
6464
name: ClassInstanceRef<String>,
6565
) -> Result<ClassInstanceRef<Class>> {
6666
tracing::debug!("java.net.URLClassLoader::findClass({this:?}, {name:?})");
6767

6868
let name_str = JavaLangString::to_rust_string(jvm, &name).await?;
6969

70-
// find rustjar first
71-
let urls = jvm.get_field(&this, "urls", "[Ljava/net/URL;").await?;
72-
let urls: Vec<ClassInstanceRef<URL>> = jvm.load_array(&urls, 0, jvm.array_length(&urls).await? as _).await?;
73-
74-
for url in urls {
75-
let file = jvm.invoke_virtual(&url, "getFile", "()Ljava/lang/String;", ()).await?;
76-
let file = JavaLangString::to_rust_string(jvm, &file).await?;
77-
78-
if file.ends_with(".rustjar") {
79-
let class = context.find_rustjar_class(jvm, &file, &name_str).await?;
80-
if let Some(class) = class {
81-
let java_class = jvm.register_class(class, Some(this.into())).await?.unwrap();
82-
83-
return Ok(java_class.into());
84-
}
85-
}
86-
}
87-
8870
let resource_name = format!("{}.class", name_str.replace('.', "/"));
8971
let resource_name = JavaLangString::from_rust_string(jvm, &resource_name).await?;
9072

@@ -130,12 +112,16 @@ impl URLClassLoader {
130112
for url in urls {
131113
let file = jvm.invoke_virtual(&url, "getFile", "()Ljava/lang/String;", ()).await?;
132114
let file = JavaLangString::to_rust_string(jvm, &file).await?;
133-
if file.ends_with('/') || file.is_empty() {
115+
116+
let metadata = runtime.metadata(&file).await;
117+
if file.ends_with('/') || file.is_empty() || metadata.as_ref().is_ok_and(|metadata| metadata.r#type == FileType::Directory) {
134118
// directory
135-
let final_path = if file.ends_with('/') {
119+
let final_path = if file.is_empty() {
120+
name_str.clone()
121+
} else if file.ends_with('/') {
136122
format!("{file}{name_str}")
137123
} else {
138-
name_str.clone()
124+
format!("{file}/{name_str}")
139125
};
140126

141127
if runtime.metadata(&final_path).await.is_ok() {
@@ -153,8 +139,8 @@ impl URLClassLoader {
153139

154140
return Ok(new_url.into());
155141
}
156-
} else if file.ends_with(".rustjar") {
157-
// TODO rustjar resource
142+
} else if metadata.is_err() {
143+
continue;
158144
} else {
159145
// treat as jar
160146
let name_str = name_str.trim_start_matches('/');

java_runtime/src/classes/java/net/url_stream_handler.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,11 @@ impl URLStreamHandler {
9595

9696
let protocol = parsed_url.scheme();
9797
let path = parsed_url.path().to_owned() + &parsed_url.query().map(|x| "?".to_owned() + x).unwrap_or("".into());
98-
// TODO handle more elegantly..
99-
let file = if protocol == "file" { path.trim_start_matches('/') } else { &path };
98+
let file = if protocol == "file" && spec_str.strip_prefix("file:").is_some_and(|file| !file.starts_with('/')) {
99+
path.trim_start_matches('/')
100+
} else {
101+
&path
102+
};
100103

101104
let protocol = JavaLangString::from_rust_string(jvm, parsed_url.scheme()).await?;
102105
let host = JavaLangString::from_rust_string(jvm, parsed_url.host_str().unwrap_or("")).await?;
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1+
pub mod lang;
12
pub mod net;
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mod rust_jar_class_loader;
2+
3+
pub use rust_jar_class_loader::RustJarClassLoader;
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use alloc::{vec, vec::Vec};
2+
3+
use java_class_proto::{JavaFieldProto, JavaMethodProto};
4+
use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString};
5+
6+
use crate::{
7+
RuntimeClassProto, RuntimeContext,
8+
classes::java::lang::{Class, ClassLoader, String},
9+
};
10+
11+
// class org.rustjava.lang.RustJarClassLoader
12+
pub struct RustJarClassLoader;
13+
14+
impl RustJarClassLoader {
15+
pub fn as_proto() -> RuntimeClassProto {
16+
RuntimeClassProto {
17+
name: "org/rustjava/lang/RustJarClassLoader",
18+
parent_class: Some("java/lang/ClassLoader"),
19+
interfaces: vec![],
20+
methods: vec![
21+
JavaMethodProto::new("<init>", "([Ljava/lang/String;Ljava/lang/ClassLoader;)V", Self::init, Default::default()),
22+
JavaMethodProto::new("findClass", "(Ljava/lang/String;)Ljava/lang/Class;", Self::find_class, Default::default()),
23+
],
24+
fields: vec![JavaFieldProto::new("classPaths", "[Ljava/lang/String;", Default::default())],
25+
access_flags: Default::default(),
26+
}
27+
}
28+
29+
async fn init(
30+
jvm: &Jvm,
31+
_: &mut RuntimeContext,
32+
mut this: ClassInstanceRef<Self>,
33+
class_paths: ClassInstanceRef<Array<String>>,
34+
parent: ClassInstanceRef<ClassLoader>,
35+
) -> Result<()> {
36+
tracing::debug!("org.rustjava.lang.RustJarClassLoader::<init>({this:?}, {class_paths:?}, {parent:?})");
37+
38+
let _: () = jvm
39+
.invoke_special(&this, "java/lang/ClassLoader", "<init>", "(Ljava/lang/ClassLoader;)V", (parent,))
40+
.await?;
41+
42+
jvm.put_field(&mut this, "classPaths", "[Ljava/lang/String;", class_paths).await?;
43+
44+
Ok(())
45+
}
46+
47+
async fn find_class(
48+
jvm: &Jvm,
49+
runtime: &mut RuntimeContext,
50+
this: ClassInstanceRef<Self>,
51+
name: ClassInstanceRef<String>,
52+
) -> Result<ClassInstanceRef<Class>> {
53+
tracing::debug!("org.rustjava.lang.RustJarClassLoader::findClass({this:?}, {name:?})");
54+
55+
let name = JavaLangString::to_rust_string(jvm, &name).await?;
56+
let class_paths = jvm.get_field(&this, "classPaths", "[Ljava/lang/String;").await?;
57+
let class_paths: Vec<ClassInstanceRef<String>> = jvm.load_array(&class_paths, 0, jvm.array_length(&class_paths).await? as usize).await?;
58+
59+
for class_path in class_paths {
60+
let class_path = JavaLangString::to_rust_string(jvm, &class_path).await?;
61+
if !class_path.ends_with(".rustjar") {
62+
continue;
63+
}
64+
65+
if let Some(class) = runtime.find_rustjar_class(jvm, &class_path, &name).await? {
66+
let class = jvm.register_class(class, Some(this.clone().into())).await?;
67+
return Ok(class.into());
68+
}
69+
}
70+
71+
Ok(None.into())
72+
}
73+
}

java_runtime/src/loader.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -168,17 +168,18 @@ pub fn get_runtime_class_proto(name: &str) -> Option<RuntimeClassProto> {
168168
crate::classes::org::rustjava::net::FileURLHandler::as_proto(),
169169
crate::classes::org::rustjava::net::JarURLConnection::as_proto(),
170170
crate::classes::org::rustjava::net::JarURLHandler::as_proto(),
171+
crate::classes::org::rustjava::lang::RustJarClassLoader::as_proto(),
171172
];
172173

173174
protos.into_iter().find(|proto| proto.name == name)
174175
}
175176

176-
struct JavaRuntimeClassLoader {
177+
struct JavaRuntimeBootstrapClassLoader {
177178
runtime: Box<dyn Runtime>,
178179
}
179180

180181
#[async_trait::async_trait]
181-
impl BootstrapClassLoader for JavaRuntimeClassLoader {
182+
impl BootstrapClassLoader for JavaRuntimeBootstrapClassLoader {
182183
async fn load_class(&self, jvm: &Jvm, name: &str) -> Result<Option<Box<dyn ClassDefinition>>> {
183184
if let Some(element_type_name) = name.strip_prefix('[') {
184185
return Ok(Some(self.runtime.define_array_class(jvm, element_type_name).await?));
@@ -189,5 +190,5 @@ impl BootstrapClassLoader for JavaRuntimeClassLoader {
189190
}
190191

191192
pub fn get_bootstrap_class_loader(runtime: Box<dyn Runtime>) -> impl BootstrapClassLoader {
192-
JavaRuntimeClassLoader { runtime }
193+
JavaRuntimeBootstrapClassLoader { runtime }
193194
}

java_runtime/tests/classes/java/io/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ mod test_byte_array_input_stream;
33
mod test_byte_array_output_stream;
44
mod test_data_input_stream;
55
mod test_data_output_stream;
6+
mod test_file;
67
mod test_file_input_stream;
78
mod test_input_stream_reader;
89
mod test_output_stream_writer;
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
use java_runtime::classes::java::lang::String;
2+
use jvm::{ClassInstanceRef, JavaChar, Result, runtime::JavaLangString};
3+
4+
use test_utils::test_jvm;
5+
6+
#[tokio::test]
7+
async fn test_platform_separators() -> Result<()> {
8+
let jvm = test_jvm().await?;
9+
10+
let separator_char: JavaChar = jvm.get_static_field("java/io/File", "separatorChar", "C").await?;
11+
let separator: ClassInstanceRef<String> = jvm.get_static_field("java/io/File", "separator", "Ljava/lang/String;").await?;
12+
let path_separator_char: JavaChar = jvm.get_static_field("java/io/File", "pathSeparatorChar", "C").await?;
13+
let path_separator: ClassInstanceRef<String> = jvm.get_static_field("java/io/File", "pathSeparator", "Ljava/lang/String;").await?;
14+
15+
if cfg!(windows) {
16+
assert_eq!(separator_char, '\\' as JavaChar);
17+
assert_eq!(JavaLangString::to_rust_string(&jvm, &separator).await?, "\\");
18+
assert_eq!(path_separator_char, ';' as JavaChar);
19+
assert_eq!(JavaLangString::to_rust_string(&jvm, &path_separator).await?, ";");
20+
} else {
21+
assert_eq!(separator_char, '/' as JavaChar);
22+
assert_eq!(JavaLangString::to_rust_string(&jvm, &separator).await?, "/");
23+
assert_eq!(path_separator_char, ':' as JavaChar);
24+
assert_eq!(JavaLangString::to_rust_string(&jvm, &path_separator).await?, ":");
25+
}
26+
27+
Ok(())
28+
}

0 commit comments

Comments
 (0)