/
class_path.rs
104 lines (92 loc) · 3.67 KB
/
class_path.rs
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
use log::debug;
use thiserror::Error;
use crate::{
class_path_entry::{ClassLoadingError, ClassPathEntry},
file_system_class_path_entry::FileSystemClassPathEntry,
jar_file_class_path_entry::JarFileClassPathEntry,
};
/// Models a class path, i.e. a list of [ClassPathEntry]
#[allow(dead_code)]
#[derive(Default, Debug)]
pub struct ClassPath {
entries: Vec<Box<dyn ClassPathEntry>>,
}
/// Error that models the fact that a class path entry was not valid
#[derive(Error, Debug, PartialEq)]
pub enum ClassPathParseError {
#[error("invalid classpath entry: {0}")]
InvalidEntry(String),
}
impl ClassPath {
/// Parses and adds class path entries.
/// These should be separated by a colon (:), just like in a real JVM.
pub fn push(&mut self, string: &str) -> Result<(), ClassPathParseError> {
let mut entries_to_add: Vec<Box<dyn ClassPathEntry>> = Vec::new();
for entry in string.split(':') {
debug!("trying to parse class path entry {}", entry);
let parsed_entry = Self::try_parse_entry(entry)?;
entries_to_add.push(parsed_entry);
}
self.entries.append(&mut entries_to_add);
Ok(())
}
fn try_parse_entry(path: &str) -> Result<Box<dyn ClassPathEntry>, ClassPathParseError> {
Self::try_parse_entry_as_jar(path).or_else(|_| Self::try_parse_entry_as_directory(path))
}
fn try_parse_entry_as_jar(path: &str) -> Result<Box<dyn ClassPathEntry>, ClassPathParseError> {
let entry = JarFileClassPathEntry::new(path)
.map_err(|_| ClassPathParseError::InvalidEntry(path.to_string()))?;
Ok(Box::new(entry))
}
fn try_parse_entry_as_directory(
path: &str,
) -> Result<Box<dyn ClassPathEntry>, ClassPathParseError> {
let entry = FileSystemClassPathEntry::new(path)
.map_err(|_| ClassPathParseError::InvalidEntry(path.to_string()))?;
Ok(Box::new(entry))
}
/// Attempts to resolve a class from the various entries.
/// Stops at the first entry that has a match or an error.
pub fn resolve(&self, class_name: &str) -> Result<Option<Vec<u8>>, ClassLoadingError> {
for entry in self.entries.iter() {
debug!("looking up class {} in {:?}", class_name, entry);
let entry_result = entry.resolve(class_name)?;
if let Some(class_bytes) = entry_result {
return Ok(Some(class_bytes));
}
}
Ok(None)
}
}
#[cfg(test)]
mod tests {
use super::ClassPath;
#[test]
fn can_parse_valid_classpath_entries() {
let dir = env!("CARGO_MANIFEST_DIR");
let mut class_path: ClassPath = Default::default();
class_path
.push(&format!(
"{dir}/tests/resources/sample.jar:{dir}/tests/resources",
))
.expect("should be able to parse classpath");
assert_can_find_class(&class_path, "rjvm/NumericTypes"); // From jar
assert_can_find_class(&class_path, "rjvm/SimpleMain"); // From directory
assert_cannot_find_class(&class_path, "foo");
}
fn assert_can_find_class(class_path: &ClassPath, class_name: &str) {
let buf = class_path
.resolve(class_name)
.expect("should not have had any errors")
.expect("should have been able to find file");
let magic_number =
u32::from_be_bytes(buf[0..4].try_into().expect("file should have 4 bytes"));
assert_eq!(0xCAFEBABE, magic_number);
}
fn assert_cannot_find_class(class_path: &ClassPath, class_name: &str) {
assert!(class_path
.resolve(class_name)
.expect("should not have had any errors")
.is_none());
}
}