Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 56 additions & 2 deletions java_runtime/src/classes/java/io/file.rs
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
use alloc::vec;

use java_class_proto::{JavaFieldProto, JavaMethodProto};
use jvm::{ClassInstanceRef, Jvm, Result, runtime::JavaLangString};
use java_constants::{FieldAccessFlags, MethodAccessFlags};
use jvm::{ClassInstanceRef, JavaChar, Jvm, Result, runtime::JavaLangString};

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

Expand All@@ -15,6 +16,7 @@ impl File {
parent_class: Some("java/lang/Object"),
interfaces: vec![],
methods: vec![
JavaMethodProto::new("<clinit>", "()V", Self::clinit, MethodAccessFlags::STATIC),
JavaMethodProto::new("<init>", "(Ljava/lang/String;)V", Self::init, Default::default()),
JavaMethodProto::new("getPath", "()Ljava/lang/String;", Self::get_path, Default::default()),
JavaMethodProto::new("exists", "()Z", Self::exists, Default::default()),
Expand All@@ -23,11 +25,63 @@ impl File {
JavaMethodProto::new("delete", "()Z", Self::delete, Default::default()),
JavaMethodProto::new("length", "()J", Self::length, Default::default()),
],
fields: vec![JavaFieldProto::new("path", "Ljava/lang/String;", Default::default())],
fields: vec![
JavaFieldProto::new(
"separatorChar",
"C",
FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL,
),
JavaFieldProto::new(
"separator",
"Ljava/lang/String;",
FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL,
),
JavaFieldProto::new(
"pathSeparatorChar",
"C",
FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL,
),
JavaFieldProto::new(
"pathSeparator",
"Ljava/lang/String;",
FieldAccessFlags::PUBLIC | FieldAccessFlags::STATIC | FieldAccessFlags::FINAL,
),
JavaFieldProto::new("path", "Ljava/lang/String;", Default::default()),
],
access_flags: Default::default(),
}
}

async fn clinit(jvm: &Jvm, _: &mut RuntimeContext) -> Result<()> {
tracing::debug!("java.io.File::<clinit>()");

let separator_char = if cfg!(windows) { '\\' } else { '/' };
let separator = if cfg!(windows) { "\\" } else { "/" };
let path_separator_char = if cfg!(windows) { ';' } else { ':' };
let path_separator = if cfg!(windows) { ";" } else { ":" };

jvm.put_static_field("java/io/File", "separatorChar", "C", separator_char as JavaChar)
.await?;
jvm.put_static_field(
"java/io/File",
"separator",
"Ljava/lang/String;",
JavaLangString::from_rust_string(jvm, separator).await?,
)
.await?;
jvm.put_static_field("java/io/File", "pathSeparatorChar", "C", path_separator_char as JavaChar)
.await?;
jvm.put_static_field(
"java/io/File",
"pathSeparator",
"Ljava/lang/String;",
JavaLangString::from_rust_string(jvm, path_separator).await?,
)
.await?;

Ok(())
}

async fn init(jvm: &Jvm, _: &mut RuntimeContext, mut this: ClassInstanceRef<Self>, pathname: ClassInstanceRef<String>) -> Result<()> {
tracing::debug!("java.io.File::<init>({this:?}, {pathname:?})");

Expand Down
37 changes: 27 additions & 10 deletions java_runtime/src/classes/java/lang/class_loader.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -97,28 +97,45 @@ impl ClassLoader {
)
.await?;

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

let mut class_paths = Vec::new();
let mut urls = Vec::new();
for path in class_path.split(':') {
// TODO File.pathSeparator
for path in class_path.split(path_separator.as_str()) {
class_paths.push(JavaLangString::from_rust_string(jvm, path).await?);

let path = JavaLangString::from_rust_string(jvm, &format!("file:{path}")).await?;
let url = jvm.new_class("java/net/URL", "(Ljava/lang/String;)V", (path,)).await?;

urls.push(url);
}

let mut url_array = jvm.instantiate_array("Ljava/net/URL;", urls.len()).await?;
jvm.store_array(&mut url_array, 0, urls).await?;

url_array
(class_paths, urls)
} else {
jvm.instantiate_array("Ljava/net/URL;", 0).await?
(Vec::new(), Vec::new())
};

let mut class_path_array = jvm.instantiate_array("Ljava/lang/String;", class_paths.len()).await?;
jvm.store_array(&mut class_path_array, 0, class_paths).await?;
let rustjar_class_loader = jvm
.new_class(
"org/rustjava/lang/RustJarClassLoader",
"([Ljava/lang/String;Ljava/lang/ClassLoader;)V",
(class_path_array, None),
)
.await?;

let mut url_array = jvm.instantiate_array("Ljava/net/URL;", urls.len()).await?;
jvm.store_array(&mut url_array, 0, urls).await?;

let url_class_loader = jvm
.new_class("java/net/URLClassLoader", "([Ljava/net/URL;Ljava/lang/ClassLoader;)V", (url_array, None))
.new_class(
"java/net/URLClassLoader",
"([Ljava/net/URL;Ljava/lang/ClassLoader;)V",
(url_array, rustjar_class_loader),
)
.await?;

let class_loader_type: ClassInstanceRef<String> = jvm
Expand Down
36 changes: 11 additions & 25 deletions java_runtime/src/classes/java/net/url_class_loader.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -7,7 +7,7 @@ use jvm::{
};

use crate::{
RuntimeClassProto, RuntimeContext,
FileType, RuntimeClassProto, RuntimeContext,
classes::java::{
lang::{Class, ClassLoader, String},
net::{JarURLConnection, URL},
Expand DownExpand Up@@ -59,32 +59,14 @@ impl URLClassLoader {

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

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

// find rustjar first
let urls = jvm.get_field(&this, "urls", "[Ljava/net/URL;").await?;
let urls: Vec<ClassInstanceRef<URL>> = jvm.load_array(&urls, 0, jvm.array_length(&urls).await? as _).await?;

for url in urls {
let file = jvm.invoke_virtual(&url, "getFile", "()Ljava/lang/String;", ()).await?;
let file = JavaLangString::to_rust_string(jvm, &file).await?;

if file.ends_with(".rustjar") {
let class = context.find_rustjar_class(jvm, &file, &name_str).await?;
if let Some(class) = class {
let java_class = jvm.register_class(class, Some(this.into())).await?.unwrap();

return Ok(java_class.into());
}
}
}

let resource_name = format!("{}.class", name_str.replace('.', "/"));
let resource_name = JavaLangString::from_rust_string(jvm, &resource_name).await?;

Expand DownExpand Up@@ -130,12 +112,16 @@ impl URLClassLoader {
for url in urls {
let file = jvm.invoke_virtual(&url, "getFile", "()Ljava/lang/String;", ()).await?;
let file = JavaLangString::to_rust_string(jvm, &file).await?;
if file.ends_with('/') || file.is_empty() {

let metadata = runtime.metadata(&file).await;
if file.ends_with('/') || file.is_empty() || metadata.as_ref().is_ok_and(|metadata| metadata.r#type == FileType::Directory) {
// directory
let final_path = if file.ends_with('/') {
let final_path = if file.is_empty() {
name_str.clone()
} else if file.ends_with('/') {
format!("{file}{name_str}")
} else {
name_str.clone()
format!("{file}/{name_str}")
};

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

return Ok(new_url.into());
}
} else if file.ends_with(".rustjar") {
// TODO rustjar resource
} else if metadata.is_err() {
continue;
} else {
// treat as jar
let name_str = name_str.trim_start_matches('/');
Expand Down
7 changes: 5 additions & 2 deletions java_runtime/src/classes/java/net/url_stream_handler.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -95,8 +95,11 @@ impl URLStreamHandler {

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

let protocol = JavaLangString::from_rust_string(jvm, parsed_url.scheme()).await?;
let host = JavaLangString::from_rust_string(jvm, parsed_url.host_str().unwrap_or("")).await?;
Expand Down
1 change: 1 addition & 0 deletions java_runtime/src/classes/org/rustjava.rs
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
pub mod lang;
pub mod net;
3 changes: 3 additions & 0 deletions java_runtime/src/classes/org/rustjava/lang.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
mod rust_jar_class_loader;

pub use rust_jar_class_loader::RustJarClassLoader;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
use alloc::{vec, vec::Vec};

use java_class_proto::{JavaFieldProto, JavaMethodProto};
use jvm::{Array, ClassInstanceRef, Jvm, Result, runtime::JavaLangString};

use crate::{
RuntimeClassProto, RuntimeContext,
classes::java::lang::{Class, ClassLoader, String},
};

// class org.rustjava.lang.RustJarClassLoader
pub struct RustJarClassLoader;

impl RustJarClassLoader {
pub fn as_proto() -> RuntimeClassProto {
RuntimeClassProto {
name: "org/rustjava/lang/RustJarClassLoader",
parent_class: Some("java/lang/ClassLoader"),
interfaces: vec![],
methods: vec![
JavaMethodProto::new("<init>", "([Ljava/lang/String;Ljava/lang/ClassLoader;)V", Self::init, Default::default()),
JavaMethodProto::new("findClass", "(Ljava/lang/String;)Ljava/lang/Class;", Self::find_class, Default::default()),
],
fields: vec![JavaFieldProto::new("classPaths", "[Ljava/lang/String;", Default::default())],
access_flags: Default::default(),
}
}

async fn init(
jvm: &Jvm,
_: &mut RuntimeContext,
mut this: ClassInstanceRef<Self>,
class_paths: ClassInstanceRef<Array<String>>,
parent: ClassInstanceRef<ClassLoader>,
) -> Result<()> {
tracing::debug!("org.rustjava.lang.RustJarClassLoader::<init>({this:?}, {class_paths:?}, {parent:?})");

let _: () = jvm
.invoke_special(&this, "java/lang/ClassLoader", "<init>", "(Ljava/lang/ClassLoader;)V", (parent,))
.await?;

jvm.put_field(&mut this, "classPaths", "[Ljava/lang/String;", class_paths).await?;

Ok(())
}

async fn find_class(
jvm: &Jvm,
runtime: &mut RuntimeContext,
this: ClassInstanceRef<Self>,
name: ClassInstanceRef<String>,
) -> Result<ClassInstanceRef<Class>> {
tracing::debug!("org.rustjava.lang.RustJarClassLoader::findClass({this:?}, {name:?})");

let name = JavaLangString::to_rust_string(jvm, &name).await?;
let class_paths = jvm.get_field(&this, "classPaths", "[Ljava/lang/String;").await?;
let class_paths: Vec<ClassInstanceRef<String>> = jvm.load_array(&class_paths, 0, jvm.array_length(&class_paths).await? as usize).await?;

for class_path in class_paths {
let class_path = JavaLangString::to_rust_string(jvm, &class_path).await?;
if !class_path.ends_with(".rustjar") {
continue;
}

if let Some(class) = runtime.find_rustjar_class(jvm, &class_path, &name).await? {
let class = jvm.register_class(class, Some(this.clone().into())).await?;
return Ok(class.into());
}
}

Ok(None.into())
}
}
7 changes: 4 additions & 3 deletions java_runtime/src/loader.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -168,17 +168,18 @@ pub fn get_runtime_class_proto(name: &str) -> Option<RuntimeClassProto> {
crate::classes::org::rustjava::net::FileURLHandler::as_proto(),
crate::classes::org::rustjava::net::JarURLConnection::as_proto(),
crate::classes::org::rustjava::net::JarURLHandler::as_proto(),
crate::classes::org::rustjava::lang::RustJarClassLoader::as_proto(),
];

protos.into_iter().find(|proto| proto.name == name)
}

struct JavaRuntimeClassLoader {
struct JavaRuntimeBootstrapClassLoader {
runtime: Box<dyn Runtime>,
}

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

pub fn get_bootstrap_class_loader(runtime: Box<dyn Runtime>) -> impl BootstrapClassLoader {
JavaRuntimeClassLoader { runtime }
JavaRuntimeBootstrapClassLoader { runtime }
}
1 change: 1 addition & 0 deletions java_runtime/tests/classes/java/io/mod.rs
Original file line numberDiff line numberDiff line change
Expand Up@@ -3,6 +3,7 @@ mod test_byte_array_input_stream;
mod test_byte_array_output_stream;
mod test_data_input_stream;
mod test_data_output_stream;
mod test_file;
mod test_file_input_stream;
mod test_input_stream_reader;
mod test_output_stream_writer;
Expand Down
28 changes: 28 additions & 0 deletions java_runtime/tests/classes/java/io/test_file.rs
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
use java_runtime::classes::java::lang::String;
use jvm::{ClassInstanceRef, JavaChar, Result, runtime::JavaLangString};

use test_utils::test_jvm;

#[tokio::test]
async fn test_platform_separators() -> Result<()> {
let jvm = test_jvm().await?;

let separator_char: JavaChar = jvm.get_static_field("java/io/File", "separatorChar", "C").await?;
let separator: ClassInstanceRef<String> = jvm.get_static_field("java/io/File", "separator", "Ljava/lang/String;").await?;
let path_separator_char: JavaChar = jvm.get_static_field("java/io/File", "pathSeparatorChar", "C").await?;
let path_separator: ClassInstanceRef<String> = jvm.get_static_field("java/io/File", "pathSeparator", "Ljava/lang/String;").await?;

if cfg!(windows) {
assert_eq!(separator_char, '\\' as JavaChar);
assert_eq!(JavaLangString::to_rust_string(&jvm, &separator).await?, "\\");
assert_eq!(path_separator_char, ';' as JavaChar);
assert_eq!(JavaLangString::to_rust_string(&jvm, &path_separator).await?, ";");
} else {
assert_eq!(separator_char, '/' as JavaChar);
assert_eq!(JavaLangString::to_rust_string(&jvm, &separator).await?, "/");
assert_eq!(path_separator_char, ':' as JavaChar);
assert_eq!(JavaLangString::to_rust_string(&jvm, &path_separator).await?, ":");
}

Ok(())
}
Loading