use std::collections::HashMap;
use yog_data::Schema;
#[derive(Schema, Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum Side {
#[default]
Shared,
Client,
Server,
}
#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
pub struct Member {
pub name: String,
pub desc: String,
pub access: u16,
}
#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
pub struct Class {
pub name: String,
pub side: Side,
pub access: u16,
pub extends: Option<String>,
pub implements: Vec<String>,
pub fields: Vec<Member>,
pub methods: Vec<Member>,
}
#[derive(Schema, Debug, Clone, Default, PartialEq, Eq)]
pub struct ClassIndex {
pub mc: String,
pub classes: Vec<Class>,
}
pub mod access;
pub struct Lookup<'i> {
by_name: HashMap<&'i str, &'i Class>,
by_simple: HashMap<&'i str, Vec<&'i Class>>,
}
impl ClassIndex {
pub fn lookup(&self) -> Lookup<'_> {
let mut by_name = HashMap::new();
let mut by_simple: HashMap<&str, Vec<&Class>> = HashMap::new();
for c in &self.classes {
by_name.insert(c.name.as_str(), c);
by_simple.entry(simple_name(&c.name)).or_default().push(c);
}
Lookup { by_name, by_simple }
}
}
pub fn simple_name(internal: &str) -> &str {
let tail = internal.rsplit('/').next().unwrap_or(internal);
tail.rsplit('$').next().unwrap_or(tail)
}
impl<'i> Lookup<'i> {
pub fn class(&self, name: &str) -> Option<&'i Class> {
self.by_name.get(name).copied()
}
pub fn simple(&self, name: &str) -> &[&'i Class] {
self.by_simple
.get(name)
.map(Vec::as_slice)
.unwrap_or_default()
}
pub fn ancestry(&self, class: &'i Class) -> Vec<&'i Class> {
let mut out = vec![class];
while let Some(parent) = out
.last()
.and_then(|c| c.extends.as_deref())
.and_then(|p| self.class(p))
{
if out.iter().any(|c| c.name == parent.name) {
break;
}
out.push(parent);
}
out
}
pub fn methods(&self, class: &'i Class, name: &str) -> Vec<&'i Member> {
let mut out = Vec::new();
let mut seen = Vec::new();
self.collect_methods(class, name, &mut out, &mut seen);
out
}
fn collect_methods(
&self,
class: &'i Class,
name: &str,
out: &mut Vec<&'i Member>,
seen: &mut Vec<&'i str>,
) {
if seen.contains(&class.name.as_str()) {
return;
}
seen.push(&class.name);
for m in class.methods.iter().filter(|m| m.name == name) {
if !out.iter().any(|o| o.desc == m.desc) {
out.push(m);
}
}
let parents = class.extends.iter().chain(&class.implements);
for p in parents.filter_map(|p| self.class(p)) {
self.collect_methods(p, name, out, seen);
}
}
pub fn field(&self, class: &'i Class, name: &str) -> Option<&'i Member> {
self.ancestry(class)
.into_iter()
.find_map(|c| c.fields.iter().find(|f| f.name == name))
}
}