mod run;
pub use run::{MethodCall, OoHost, Returned, call_through_pointer, compare_references, invoke, jni_environment, self_reference};
use numeric::assumptions::REFERENCES_KEPT;
use std::collections::HashMap;
pub const JAVA_LANG_OBJECT: &str = "java.lang.Object";
pub const MAX_OBJECTS: usize = 1_000_000;
pub const MAX_REFERENCES: usize = 1 << 23;
pub const MAX_MEMORY: usize = 1 << 30;
const GLOBAL: u32 = u32::MAX;
pub struct Objects<C> {
pub classes: Vec<LoadedClass<C>>,
pub objects: Vec<Instance>,
pub jni: Option<usize>,
references: Vec<Reference>,
frames: Vec<LocalFrame>,
serials: u32,
events: Vec<String>,
event_index: HashMap<String, u32>,
}
struct Reference {
object: u32,
frame: u32,
made: u32,
freed: u32,
}
struct LocalFrame {
serial: u32,
first: u32,
pushed: bool,
}
pub enum Referent {
Null,
Object(u32),
Expired(String),
Unknown,
}
pub struct LoadedClass<C> {
pub external: String,
pub code: Option<C>,
pub parent: Option<usize>,
pub factory_object: u32,
pub factory_data: Option<usize>,
pub methods: Vec<Option<usize>>,
pub sources: Vec<String>,
}
pub struct Instance {
pub class: usize,
pub factory: bool,
pub parts: Vec<(usize, usize)>,
}
pub struct ClassCode<H> {
pub parent: String,
pub factory: Option<Part<H>>,
pub object: Option<Part<H>>,
pub methods: Vec<MethodCode<H>>,
}
pub struct Part<H> {
pub data: H,
pub records: Vec<u32>,
}
pub struct MethodCode<H> {
pub name: String,
pub factory: bool,
pub params: Vec<String>,
pub returns: Option<String>,
pub code: H,
pub own_records: usize,
}
#[derive(Clone, Copy, Debug, Default)]
pub struct Frame {
pub method: Option<Running>,
}
#[derive(Clone, Copy, Debug)]
pub struct Running {
pub class: usize,
pub factory: bool,
pub this: u32,
pub cell: usize,
pub frame: u32,
pub invoked: u32,
}
impl<C> Default for Objects<C> {
fn default() -> Self {
Self { classes: Vec::new(), objects: Vec::new(), jni: None, references: Vec::new(), frames: Vec::new(), serials: 0, events: Vec::new(), event_index: HashMap::new() }
}
}
impl<C> Objects<C> {
pub fn find(&self, external: &str) -> Option<usize> {
self.classes.iter().position(|c| c.external == external)
}
pub fn object(&self, id: u32) -> Option<&Instance> {
(id as usize).checked_sub(1).and_then(|i| self.objects.get(i))
}
pub fn add_object(&mut self, object: Instance) -> Result<u32, String> {
if self.objects.len() >= MAX_OBJECTS {
return Err(format!("the run unit created more than {MAX_OBJECTS} objects, which ironwork for COBOL never frees"));
}
self.objects.push(object);
Ok(self.objects.len() as u32)
}
pub fn event(&mut self, text: String) -> u32 {
if let Some(&n) = self.event_index.get(&text) {
return n;
}
let n = self.events.len() as u32;
self.event_index.insert(text.clone(), n);
self.events.push(text);
n
}
pub fn told(&self, event: u32) -> &str {
self.events.get(event as usize).map_or("", String::as_str)
}
fn make(&mut self, object: u32, frame: u32, made: u32) -> Result<u32, String> {
if self.references.len() >= MAX_REFERENCES {
return Err(format!(
"the run unit made more than {MAX_REFERENCES} object references, which ironwork for COBOL keeps so that an expired one can say where it expired (see {REFERENCES_KEPT})"
));
}
self.references.push(Reference { object, frame, made, freed: 0 });
Ok(self.references.len() as u32)
}
pub fn local(&mut self, object: u32, made: u32) -> Result<u32, String> {
let frame = self.frames.last().map_or(0, |f| f.serial);
self.make(object, frame, made)
}
pub fn local_in(&mut self, frame: u32, object: u32, made: u32) -> Result<u32, String> {
self.make(object, frame, made)
}
pub fn global(&mut self, object: u32, made: u32) -> Result<u32, String> {
self.make(object, GLOBAL, made)
}
pub fn push_frame(&mut self, pushed: bool) -> (usize, u32) {
self.serials = if self.serials >= GLOBAL - 1 { 1 } else { self.serials + 1 };
self.frames.push(LocalFrame { serial: self.serials, first: self.references.len() as u32, pushed });
(self.frames.len() - 1, self.serials)
}
pub fn pop_frames(&mut self, depth: usize, event: u32) {
let Some(first) = self.frames.get(depth).map(|f| f.first as usize) else { return };
let serials: Vec<u32> = self.frames.drain(depth..).map(|f| f.serial).collect();
for r in self.references.iter_mut().skip(first) {
if r.freed == 0 && serials.contains(&r.frame) {
r.freed = event + 1;
}
}
}
pub fn pushed_frame(&self) -> Option<usize> {
self.frames.last().filter(|f| f.pushed).map(|_| self.frames.len() - 1)
}
pub fn referent(&self, value: u32) -> Referent {
if value == 0 {
return Referent::Null;
}
match self.references.get(value as usize - 1) {
None => Referent::Unknown,
Some(r) if r.freed == 0 => Referent::Object(r.object),
Some(r) => Referent::Expired(format!("{}; it {}", self.described(r), self.told(r.freed - 1))),
}
}
pub fn is_global(&self, value: u32) -> bool {
(value as usize).checked_sub(1).and_then(|i| self.references.get(i)).is_some_and(|r| r.frame == GLOBAL)
}
pub fn free(&mut self, value: u32, event: u32) {
if let Some(r) = (value as usize).checked_sub(1).and_then(|i| self.references.get_mut(i)) {
r.freed = event + 1;
}
}
fn described(&self, r: &Reference) -> String {
let kind = if r.frame == GLOBAL { "global" } else { "local" };
let object = match self.object(r.object) {
Some(o) if o.factory => format!("the factory object of {}", self.classes[o.class].external),
Some(o) => {
let class = &self.classes[o.class].external;
let article = if class.starts_with(['A', 'E', 'I', 'O', 'U', 'a', 'e', 'i', 'o', 'u']) { "an" } else { "a" };
format!("{article} {class} object")
}
None => "an object".into(),
};
format!("a {kind} reference to {object}, {}", self.told(r.made))
}
}