#![cfg_attr(feature = "strict", strict_mode)]
#![cfg_attr(strict_mode, deny(unused))]
#![deny(rustdoc::broken_intra_doc_links)]
#![deny(missing_docs)]
use crate::constant_pool::ConstantPoolInfo;
use std::cell::RefCell;
use std::collections::hash_map::Entry;
use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::Read;
use std::path::{Path, PathBuf};
use zip::result::ZipError;
use zip::ZipArchive;
mod constant_pool;
pub mod error;
pub mod inheritance;
pub(crate) mod raw_java_class;
mod structures;
pub(crate) mod utility;
use crate::error::{Error, ErrorKind};
pub use structures::*;
use structures::{AsFullyQualifiedName, FQName};
#[derive(Debug, Default)]
pub struct JavaClassParser {
class_path: HashSet<PathBuf>,
cache: RefCell<HashMap<FQNameBuf, JavaClass>>,
open_zips: RefCell<HashMap<PathBuf, ZipArchive<File>>>,
}
impl JavaClassParser {
pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<JavaClass, Error> {
let bytes = std::fs::read(path)?;
let raw_class = raw_java_class::parse_class_file_bytes(&bytes)?;
Ok(JavaClass::new(raw_class))
}
pub fn new<S: AsRef<str>>(classpath: S) -> Self {
Self {
class_path: classpath
.as_ref()
.split(";")
.map(|s| PathBuf::from(s))
.collect(),
..Default::default()
}
}
pub fn find<P: AsFullyQualifiedName + ?Sized>(&self, path: &P) -> Result<JavaClass, Error> {
let fcq = path.as_fcq();
if !self.cache.borrow().contains_key(fcq) {
let class = self.find_class(fcq)?;
self.cache.borrow_mut().insert(fcq.to_fqname_buf(), class);
}
Ok(self.cache.borrow()[fcq].clone())
}
pub fn find_super(&self, class: &JavaClass) -> Result<JavaClass, Error> {
let super_class = class.super_name();
self.find(super_class)
}
pub fn find_interfaces(&self, class: &JavaClass) -> Result<Vec<JavaClass>, Error> {
class
.interfaces()
.into_iter()
.filter_map(|class| match self.find(class) {
o @ Ok(_) => Some(o),
Err(e) => match e.kind() {
ErrorKind::NoClassFound(_) => None,
_ => Some(Err(e)),
},
})
.collect()
}
pub fn classpath(&self) -> impl Iterator<Item = &Path> {
self.class_path.iter().map(|p| p.as_ref())
}
fn find_class(&self, path: &FQName) -> Result<JavaClass, Error> {
let cp = self
.classpath()
.into_iter()
.map(|s| s.to_owned())
.collect::<Vec<_>>();
for entry in cp {
if let Some(found) = self.find_class_in_entry(&entry, path)? {
return Ok(found);
}
}
Err(Error::from(ErrorKind::NoClassFound(path.to_fqname_buf())))
}
fn find_class_in_entry(
&self,
entry: &Path,
fq_name: &FQName,
) -> Result<Option<JavaClass>, Error> {
if entry.is_file() {
match entry.extension().and_then(|s| s.to_str()) {
Some("class") => {
let parsed = parse_file(entry)?;
if parsed.this() == fq_name {
Ok(Some(parsed))
} else {
Ok(None)
}
}
Some("jar") => {
let mut open_zips = self.open_zips.borrow_mut();
let zip_archive = match open_zips.entry(entry.to_path_buf()) {
Entry::Occupied(o) => o.into_mut(),
Entry::Vacant(vac) => {
let file = File::open(entry)?;
vac.insert(ZipArchive::new(file)?)
}
};
let name = fq_name.as_path().with_extension("class");
let result = match zip_archive.by_name(&name.to_string_lossy()) {
Ok(archived) => parse_bytes(archived).map(|class| Some(class)),
Err(ZipError::FileNotFound) => Ok(None),
Err(e) => Err(e.into()),
};
result
}
_ => Err(ErrorKind::UnsupportedEntry(entry.to_path_buf()).into()),
}
} else if entry.is_dir() {
let full_path = entry.join(fq_name).with_extension("class");
if full_path.exists() {
let read = parse_bytes(File::open(full_path)?)?;
Ok(Some(read))
} else {
Ok(None)
}
} else {
Err(ErrorKind::UnsupportedEntry(entry.to_path_buf()).into())
}
}
}
impl<P: AsRef<Path>> From<P> for JavaClassParser {
fn from(p: P) -> Self {
Self::from_iter([p])
}
}
impl<P: AsRef<Path>> FromIterator<P> for JavaClassParser {
fn from_iter<T: IntoIterator<Item = P>>(iter: T) -> Self {
Self {
class_path: iter.into_iter().map(|p| p.as_ref().to_path_buf()).collect(),
..Default::default()
}
}
}
pub fn parse_bytes<R: Read>(mut read: R) -> Result<JavaClass, Error> {
let mut buffer = vec![];
read.read_to_end(&mut buffer)?;
raw_java_class::parse_class_file_bytes(&buffer[..]).map(JavaClass::new)
}
pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<JavaClass, Error> {
JavaClassParser::parse_file(path)
}