use std::fmt::{Debug, Display};
use crate::{Cursor, Parse, UnknownFieldType, ty::FieldType};
#[derive(Debug, Clone)]
pub enum InvalidMethodDescriptor {
BrokenBrackets,
UnknownFieldTy(UnknownFieldType),
}
impl std::error::Error for InvalidMethodDescriptor {}
impl From<UnknownFieldType> for InvalidMethodDescriptor {
fn from(value: UnknownFieldType) -> Self {
Self::UnknownFieldTy(value)
}
}
impl Display for InvalidMethodDescriptor {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::BrokenBrackets => write!(f, "broken brackets"),
Self::UnknownFieldTy(unknown_field_ty) => {
write!(f, "{unknown_field_ty}")
}
}
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct MethodDescriptor<'a> {
pub params: Box<[FieldType<'a>]>,
pub ret: MethodReturnDescriptor<'a>,
}
impl Display for MethodDescriptor<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "(")?;
for param in &self.params {
write!(f, "{param}")?;
}
write!(f, "){}", self.ret)
}
}
impl<'a> Parse<'a> for MethodDescriptor<'a> {
type Error = InvalidMethodDescriptor;
fn parse_from(cursor: &mut Cursor<'a>) -> Result<Self, Self::Error> {
if cursor.get_char() != '(' {
return Err(InvalidMethodDescriptor::BrokenBrackets);
}
let mut params_raw = Cursor::new(cursor.try_advance(|s| {
s.split_once(')')
.ok_or(InvalidMethodDescriptor::BrokenBrackets)
})?);
let mut params = vec![];
while !params_raw.get().is_empty() {
params.push(FieldType::parse_from(&mut params_raw)?);
}
Ok(Self {
params: params.into_boxed_slice(),
ret: MethodReturnDescriptor::parse_from(cursor)?,
})
}
}
#[derive(Clone, PartialEq, Eq, Hash)]
pub enum MethodReturnDescriptor<'a> {
Void,
Type(FieldType<'a>),
}
impl Display for MethodReturnDescriptor<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
MethodReturnDescriptor::Void => write!(f, "V"),
MethodReturnDescriptor::Type(field_ty) => write!(f, "{field_ty}"),
}
}
}
impl<'a> Parse<'a> for MethodReturnDescriptor<'a> {
type Error = UnknownFieldType;
fn parse_from(cursor: &mut Cursor<'a>) -> Result<Self, Self::Error> {
if cursor.get().chars().next().is_some_and(|c| c == 'V') {
cursor.advance_by('V'.len_utf8());
Ok(Self::Void)
} else {
FieldType::parse_from(cursor).map(Self::Type)
}
}
}
impl Debug for MethodReturnDescriptor<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Void => write!(f, "void"),
Self::Type(ty) => Debug::fmt(ty, f),
}
}
}
impl Debug for MethodDescriptor<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "method(")?;
let mut it = self.params.iter();
if let Some(first) = it.next() {
Debug::fmt(first, f)?;
}
for param in it {
write!(f, ", ")?;
Debug::fmt(param, f)?;
}
write!(f, ") -> ")?;
Debug::fmt(&self.ret, f)
}
}
#[cfg(test)]
mod tests {
use crate::{
CanonicalClassName, ClassName, FieldType, MethodDescriptor, MethodReturnDescriptor,
ReprForm, parse, validate_rw,
};
#[test]
fn return_desc_void() {
assert_eq!(
parse::<'_, MethodReturnDescriptor<'_>>("V").unwrap(),
MethodReturnDescriptor::Void
);
validate_rw::<'_, MethodReturnDescriptor<'_>>("V");
}
#[test]
fn return_desc_primitive() {
assert_eq!(
parse::<'_, MethodReturnDescriptor<'_>>("I").unwrap(),
MethodReturnDescriptor::Type(FieldType::Int)
);
validate_rw::<'_, MethodReturnDescriptor<'_>>("I");
}
#[test]
fn empty_to_void() {
assert_eq!(
parse::<'_, MethodDescriptor<'_>>("()V").unwrap(),
MethodDescriptor {
params: Box::new([]),
ret: MethodReturnDescriptor::Void
}
);
validate_rw::<'_, MethodDescriptor<'_>>("()V");
}
#[test]
fn mixed() {
assert_eq!(
parse::<'_, MethodDescriptor<'_>>(
"(I[BLjava/lang/String;Ljava/lang/Object;Z)[Ljava/lang/String;"
)
.unwrap(),
MethodDescriptor {
params: Box::new([
FieldType::Int,
FieldType::Array(Box::new(FieldType::Byte)),
FieldType::Class(Box::new(ClassName::TopLevel(CanonicalClassName {
package: Some("java/lang"),
simple: "String",
form: ReprForm::Internal
}))),
FieldType::Class(Box::new(ClassName::TopLevel(CanonicalClassName {
package: Some("java/lang"),
simple: "Object",
form: ReprForm::Internal
}))),
FieldType::Boolean,
]),
ret: MethodReturnDescriptor::Type(FieldType::Array(Box::new(FieldType::Class(
Box::new(ClassName::TopLevel(CanonicalClassName {
package: Some("java/lang"),
simple: "String",
form: ReprForm::Internal
}))
))))
}
);
validate_rw::<'_, MethodDescriptor<'_>>(
"(I[BLjava/lang/String;Ljava/lang/Object;Z)[Ljava/lang/String;",
);
}
}