jfrs 0.2.5

Java Flight Recorder reader for Rust
Documentation
//! Low-level representation of the decoded JFR values.

use crate::reader::byte_stream::{ByteStream, StringType};
use crate::reader::metadata::Metadata;

use crate::reader::type_descriptor::{FieldDescriptor, TypeDescriptor};
use crate::reader::{Chunk, Error, Result};
use std::io::Read;

#[derive(Debug)]
pub enum ValueDescriptor {
    Primitive(Primitive),
    Object(Object),
    Array(Vec<ValueDescriptor>),
    ConstantPool { class_id: i64, constant_index: i64 },
}

impl ValueDescriptor {
    pub fn try_new<T: Read>(
        stream: &mut ByteStream<T>,
        class_id: i64,
        metadata: &Metadata,
    ) -> Result<ValueDescriptor> {
        let type_desc = metadata
            .type_pool
            .get(class_id)
            .ok_or(Error::ClassNotFound(class_id))?;

        if let Some(value) = Self::try_read_primitive(stream, type_desc)? {
            return Ok(value);
        }

        let mut obj = Object {
            class_id: type_desc.class_id,
            fields: Vec::with_capacity(type_desc.fields.len()),
        };

        for field_desc in type_desc.fields.iter() {
            let value = if field_desc.array_type {
                let count = stream.read_i32()? as usize;
                let mut elems = Vec::with_capacity(count);
                for _ in 0..count {
                    elems.push(Self::try_read_field_single(stream, field_desc, metadata)?);
                }
                ValueDescriptor::Array(elems)
            } else {
                Self::try_read_field_single(stream, field_desc, metadata)?
            };
            obj.fields.push(value);
        }

        Ok(ValueDescriptor::Object(obj))
    }

    pub fn get_field<'a>(&'a self, name: &str, chunk: &'a Chunk) -> Option<&'a ValueDescriptor> {
        self.inner_get_field(name, chunk, true)
    }

    pub fn get_field_raw<'a>(
        &'a self,
        name: &str,
        chunk: &'a Chunk,
    ) -> Option<&'a ValueDescriptor> {
        self.inner_get_field(name, chunk, false)
    }

    fn inner_get_field<'a>(
        &'a self,
        name: &str,
        chunk: &'a Chunk,
        resolve_constant: bool,
    ) -> Option<&'a ValueDescriptor> {
        match self {
            ValueDescriptor::Object(o) => Self::get_object_field(o, name, chunk, resolve_constant),
            ValueDescriptor::ConstantPool {
                class_id,
                constant_index,
            } => match chunk.constant_pool.get(class_id, constant_index) {
                Some(ValueDescriptor::Object(o)) => {
                    Self::get_object_field(o, name, chunk, resolve_constant)
                }
                _ => None,
            },
            _ => None,
        }
    }

    fn get_object_field<'a>(
        obj: &'a Object,
        name: &str,
        chunk: &'a Chunk,
        resolve_constant: bool,
    ) -> Option<&'a ValueDescriptor> {
        let res = chunk
            .metadata
            .type_pool
            .get(obj.class_id)
            .and_then(|c| c.get_field(name))
            .and_then(|(idx, _)| obj.fields.get(idx));
        if !resolve_constant {
            return res;
        }

        match res {
            Some(ValueDescriptor::ConstantPool {
                class_id,
                constant_index,
            }) => chunk.constant_pool.get(class_id, constant_index),
            _ => res,
        }
    }

    fn try_read_field_single<T: Read>(
        stream: &mut ByteStream<T>,
        field_desc: &FieldDescriptor,
        metadata: &Metadata,
    ) -> Result<ValueDescriptor> {
        if field_desc.constant_pool {
            Ok(ValueDescriptor::ConstantPool {
                class_id: field_desc.class_id,
                constant_index: stream.read_i64()?,
            })
        } else {
            Self::try_new(stream, field_desc.class_id, metadata)
        }
    }

    fn try_read_primitive<T: Read>(
        stream: &mut ByteStream<T>,
        type_desc: &TypeDescriptor,
    ) -> Result<Option<ValueDescriptor>> {
        let value = match type_desc.name() {
            "int" => Some(ValueDescriptor::Primitive(Primitive::Integer(
                stream.read_i32()?,
            ))),
            "long" => Some(ValueDescriptor::Primitive(Primitive::Long(
                stream.read_i64()?,
            ))),
            "float" => Some(ValueDescriptor::Primitive(Primitive::Float(
                stream.read_f32()?,
            ))),
            "double" => Some(ValueDescriptor::Primitive(Primitive::Double(
                stream.read_f64()?,
            ))),
            "char" => {
                let c = stream.read_char()?;
                Some(ValueDescriptor::Primitive(Primitive::Character(
                    #[cfg(feature = "cstring")]
                    CString {
                        string: std::ffi::CString::new(c.to_string())
                            .expect("Failed to create CString"),
                        len: 1,
                    },
                    #[cfg(not(feature = "cstring"))]
                    c,
                )))
            }
            "boolean" => Some(ValueDescriptor::Primitive(Primitive::Boolean(
                stream.read_i8()? != 0,
            ))),
            "short" => Some(ValueDescriptor::Primitive(Primitive::Short(
                stream.read_i16()?,
            ))),
            "byte" => Some(ValueDescriptor::Primitive(Primitive::Byte(
                stream.read_i8()?,
            ))),
            "java.lang.String" => match stream.read_string()? {
                StringType::Null => Some(ValueDescriptor::Primitive(Primitive::NullString)),
                s @ (StringType::Empty | StringType::Raw(_)) => {
                    let s = if let StringType::Raw(s) = s {
                        s
                    } else {
                        "".to_string()
                    };
                    #[allow(unused_variables)]
                    let len = s.len();
                    Some(ValueDescriptor::Primitive(Primitive::String(
                        #[cfg(feature = "cstring")]
                        CString {
                            string: std::ffi::CString::new(s).expect("Failed to create CString"),
                            len,
                        },
                        #[cfg(not(feature = "cstring"))]
                        s,
                    )))
                }
                StringType::ConstantPool(idx) => Some(ValueDescriptor::ConstantPool {
                    class_id: type_desc.class_id,
                    constant_index: idx,
                }),
            },
            _ => None,
        };
        Ok(value)
    }
}

#[derive(Debug)]
pub struct Object {
    pub class_id: i64,
    pub fields: Vec<ValueDescriptor>,
}

#[cfg(feature = "cstring")]
#[derive(Debug)]
pub struct CString {
    pub string: std::ffi::CString,
    pub len: usize,
}

#[derive(Debug)]
pub enum Primitive {
    Integer(i32),
    Long(i64),
    Float(f32),
    Double(f64),
    // Rust's char can't be mapped to C in natural way
    // so we just encode it as string
    #[cfg(feature = "cstring")]
    Character(CString),
    #[cfg(not(feature = "cstring"))]
    Character(char),
    Boolean(bool),
    Short(i16),
    Byte(i8),
    NullString,
    #[cfg(feature = "cstring")]
    String(CString),
    #[cfg(not(feature = "cstring"))]
    String(String),
}

#[macro_use]
mod macros {
    macro_rules! impl_try_from_primitive {
        ($variant:ident, $ty:ty) => {
            impl<'a> TryFrom<&'a ValueDescriptor> for &'a $ty {
                type Error = ();
                fn try_from(value: &'a ValueDescriptor) -> std::result::Result<Self, Self::Error> {
                    if let ValueDescriptor::Primitive(Primitive::$variant(v)) = value {
                        Ok(v)
                    } else {
                        Err(())
                    }
                }
            }

            impl<'a> TryFrom<&'a ValueDescriptor> for $ty {
                type Error = ();
                fn try_from(value: &'a ValueDescriptor) -> std::result::Result<Self, Self::Error> {
                    <&$ty>::try_from(value).map(|v| *v)
                }
            }
        };
    }
}

impl_try_from_primitive!(Integer, i32);
impl_try_from_primitive!(Long, i64);
impl_try_from_primitive!(Float, f32);
impl_try_from_primitive!(Double, f64);
#[cfg(not(feature = "cstring"))]
impl_try_from_primitive!(Character, char);
impl_try_from_primitive!(Boolean, bool);
impl_try_from_primitive!(Short, i16);
impl_try_from_primitive!(Byte, i8);

impl<'a> TryFrom<&'a ValueDescriptor> for &'a str {
    type Error = ();

    fn try_from(value: &'a ValueDescriptor) -> std::result::Result<Self, Self::Error> {
        if let ValueDescriptor::Primitive(Primitive::String(s)) = value {
            #[cfg(feature = "cstring")]
            return s.string.as_c_str().to_str().map_err(|_| ());
            #[cfg(not(feature = "cstring"))]
            return Ok(s.as_str());
        } else {
            Err(())
        }
    }
}