use std::{convert::Infallible, fmt::Display};
use crate::{Cursor, Parse, ReprForm, method::MethodDescriptor, strip_digits_prefix};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum ClassName<'a> {
TopLevel(CanonicalClassName<'a>),
#[doc(alias = "Generic")]
Member {
parent: Box<Self>,
simple: &'a str,
},
Local {
parent: Box<Self>,
simple: &'a str,
index: u32,
},
Anonymous {
parent: Box<Self>,
index: u32,
},
#[doc(alias = "ConstructorGeneric")]
MethodGeneric {
class: Box<Self>,
method: MethodDescriptor<'a>,
simple: &'a str,
},
}
impl Display for ClassName<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
ClassName::TopLevel(canonical_class_name) => write!(f, "{canonical_class_name}"),
ClassName::Member { parent, simple } => write!(f, "{parent}${simple}"),
ClassName::Local {
parent,
simple,
index,
} => write!(f, "{parent}${index}{simple}"),
ClassName::Anonymous { parent, index } => write!(f, "{parent}${index}"),
ClassName::MethodGeneric {
class,
method,
simple,
} => write!(f, "{class}${method}${simple}"),
}
}
}
impl<'a> Parse<'a> for ClassName<'a> {
type Error = Infallible;
fn parse_from(cursor: &mut Cursor<'a>) -> Result<Self, Self::Error> {
let s = cursor.get();
if let Some((parent, simple)) = s.rsplit_once('$') {
if let Some((parent, method)) = parent.rsplit_once('$')
&& method.chars().next().is_some_and(|c| c == '(')
&& let Ok(method) = MethodDescriptor::parse_from(&mut Cursor::new(method))
{
return Ok(Self::MethodGeneric {
class: Box::new(Self::parse_from(&mut Cursor::new(parent)).unwrap()),
method,
simple,
});
}
let parent = Box::new(Self::parse_from(&mut Cursor::new(parent))?);
let (digits, simple) = strip_digits_prefix(simple);
cursor.clear();
Ok(match (digits, simple.is_empty()) {
(None, _) => Self::Member { parent, simple },
(Some(index), true) => Self::Anonymous { parent, index },
(Some(index), false) => Self::Local {
parent,
simple,
index,
},
})
} else {
CanonicalClassName::parse_from(cursor).map(Self::TopLevel)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct CanonicalClassName<'a> {
pub package: Option<&'a str>,
pub simple: &'a str,
pub form: ReprForm,
}
impl Display for CanonicalClassName<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(pkg) = self.package {
write!(f, "{pkg}{}{}", self.form.package_separator(), self.simple)
} else {
write!(f, "{}", self.simple)
}
}
}
impl<'a> Parse<'a> for CanonicalClassName<'a> {
type Error = Infallible;
fn parse_from(cursor: &mut Cursor<'a>) -> Result<Self, Self::Error> {
Ok(cursor.advance(|s| {
let form = if s.contains('/') {
ReprForm::Internal
} else {
ReprForm::JLS
};
let (package, c) = s.rsplit_once(form.package_separator()).unzip();
(
Self {
package,
simple: c.unwrap_or(s),
form,
},
"",
)
}))
}
}
#[cfg(test)]
mod tests {
use crate::{CanonicalClassName, ClassName, ReprForm, parse, validate_rw};
#[test]
fn top_level() {
assert_eq!(
parse::<'_, ClassName<'_>>("java.lang.String").unwrap(),
ClassName::TopLevel(CanonicalClassName {
package: Some("java.lang"),
simple: "String",
form: ReprForm::JLS,
})
);
assert_eq!(
parse::<'_, ClassName<'_>>("Foo").unwrap(),
ClassName::TopLevel(CanonicalClassName {
package: None,
simple: "Foo",
form: ReprForm::JLS,
})
);
validate_rw::<'_, ClassName<'_>>("java.lang.String");
}
#[test]
fn top_level_jvm() {
assert_eq!(
parse::<'_, ClassName<'_>>("java/lang/String").unwrap(),
ClassName::TopLevel(CanonicalClassName {
package: Some("java/lang"),
simple: "String",
form: ReprForm::Internal
})
);
validate_rw::<'_, ClassName<'_>>("java/lang/String");
}
#[test]
fn member() {
assert_eq!(
parse::<'_, ClassName<'_>>("java.util.Map$Entry").unwrap(),
ClassName::Member {
parent: Box::new(ClassName::TopLevel(CanonicalClassName {
package: Some("java.util"),
simple: "Map",
form: ReprForm::JLS
})),
simple: "Entry"
}
);
validate_rw::<'_, ClassName<'_>>("java.util.Map$Entry");
}
#[test]
fn local() {
assert_eq!(
parse::<'_, ClassName<'_>>("com.example.OuterClass$1LocalClass").unwrap(),
ClassName::Local {
parent: Box::new(ClassName::TopLevel(CanonicalClassName {
package: Some("com.example"),
simple: "OuterClass",
form: ReprForm::JLS
})),
index: 1,
simple: "LocalClass"
}
);
validate_rw::<'_, ClassName<'_>>("com.example.OuterClass$1LocalClass");
}
#[test]
fn anonymous() {
assert_eq!(
parse::<'_, ClassName<'_>>("com.example.OuterClass$1").unwrap(),
ClassName::Anonymous {
parent: Box::new(ClassName::TopLevel(CanonicalClassName {
package: Some("com.example"),
simple: "OuterClass",
form: ReprForm::JLS
})),
index: 1,
}
);
validate_rw::<'_, ClassName<'_>>("com.example.OuterClass$1");
}
}