jni/wrapper/
signature.rs

1use std::{fmt, str::FromStr};
2
3use combine::{
4    between, many, many1, parser, satisfy, token, ParseError, Parser, StdParseResult, Stream,
5};
6
7use crate::errors::*;
8
9/// A primitive java type. These are the things that can be represented without
10/// an object.
11#[allow(missing_docs)]
12#[derive(Eq, PartialEq, Debug, Clone, Copy)]
13pub enum Primitive {
14    Boolean, // Z
15    Byte,    // B
16    Char,    // C
17    Double,  // D
18    Float,   // F
19    Int,     // I
20    Long,    // J
21    Short,   // S
22    Void,    // V
23}
24
25impl fmt::Display for Primitive {
26    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
27        match *self {
28            Primitive::Boolean => write!(f, "Z"),
29            Primitive::Byte => write!(f, "B"),
30            Primitive::Char => write!(f, "C"),
31            Primitive::Double => write!(f, "D"),
32            Primitive::Float => write!(f, "F"),
33            Primitive::Int => write!(f, "I"),
34            Primitive::Long => write!(f, "J"),
35            Primitive::Short => write!(f, "S"),
36            Primitive::Void => write!(f, "V"),
37        }
38    }
39}
40
41/// Enum representing any java type in addition to method signatures.
42#[allow(missing_docs)]
43#[derive(Eq, PartialEq, Debug, Clone)]
44pub enum JavaType {
45    Primitive(Primitive),
46    Object(String),
47    Array(Box<JavaType>),
48    Method(Box<TypeSignature>),
49}
50
51impl FromStr for JavaType {
52    type Err = Error;
53
54    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
55        parser(parse_type)
56            .parse(s)
57            .map(|res| res.0)
58            .map_err(|e| Error::ParseFailed(e, s.to_owned()))
59    }
60}
61
62impl fmt::Display for JavaType {
63    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
64        match *self {
65            JavaType::Primitive(ref ty) => ty.fmt(f),
66            JavaType::Object(ref name) => write!(f, "L{name};"),
67            JavaType::Array(ref ty) => write!(f, "[{ty}"),
68            JavaType::Method(ref m) => m.fmt(f),
69        }
70    }
71}
72
73/// Enum representing any java type that may be used as a return value
74///
75/// This type intentionally avoids capturing any heap allocated types (to avoid
76/// allocations while making JNI method calls) and so it doesn't fully qualify
77/// the object or array types with a String like `JavaType::Object` does.
78#[allow(missing_docs)]
79#[derive(Eq, PartialEq, Debug, Clone)]
80pub enum ReturnType {
81    Primitive(Primitive),
82    Object,
83    Array,
84}
85
86impl FromStr for ReturnType {
87    type Err = Error;
88
89    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
90        parser(parse_return)
91            .parse(s)
92            .map(|res| res.0)
93            .map_err(|e| Error::ParseFailed(e, s.to_owned()))
94    }
95}
96
97impl fmt::Display for ReturnType {
98    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
99        match *self {
100            ReturnType::Primitive(ref ty) => ty.fmt(f),
101            ReturnType::Object => write!(f, "L;"),
102            ReturnType::Array => write!(f, "["),
103        }
104    }
105}
106
107/// A method type signature. This is the structure representation of something
108/// like `(Ljava/lang/String;)Z`. Used by the `call_(object|static)_method`
109/// functions on jnienv to ensure safety.
110#[allow(missing_docs)]
111#[derive(Eq, PartialEq, Debug, Clone)]
112pub struct TypeSignature {
113    pub args: Vec<JavaType>,
114    pub ret: ReturnType,
115}
116
117impl TypeSignature {
118    /// Parse a signature string into a TypeSignature enum.
119    // Clippy suggests implementing `FromStr` or renaming it which is not possible in our case.
120    #[allow(clippy::should_implement_trait)]
121    pub fn from_str<S: AsRef<str>>(s: S) -> Result<TypeSignature> {
122        Ok(match parser(parse_sig).parse(s.as_ref()).map(|res| res.0) {
123            Ok(JavaType::Method(sig)) => *sig,
124            Err(e) => return Err(Error::ParseFailed(e, s.as_ref().to_owned())),
125            _ => unreachable!(),
126        })
127    }
128}
129
130impl fmt::Display for TypeSignature {
131    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
132        write!(f, "(")?;
133        for a in &self.args {
134            write!(f, "{a}")?;
135        }
136        write!(f, ")")?;
137        write!(f, "{}", self.ret)?;
138        Ok(())
139    }
140}
141
142fn parse_primitive<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Primitive, S>
143where
144    S::Error: ParseError<char, S::Range, S::Position>,
145{
146    let boolean = token('Z').map(|_| Primitive::Boolean);
147    let byte = token('B').map(|_| Primitive::Byte);
148    let char_type = token('C').map(|_| Primitive::Char);
149    let double = token('D').map(|_| Primitive::Double);
150    let float = token('F').map(|_| Primitive::Float);
151    let int = token('I').map(|_| Primitive::Int);
152    let long = token('J').map(|_| Primitive::Long);
153    let short = token('S').map(|_| Primitive::Short);
154    let void = token('V').map(|_| Primitive::Void);
155
156    (boolean
157        .or(byte)
158        .or(char_type)
159        .or(double)
160        .or(float)
161        .or(int)
162        .or(long)
163        .or(short)
164        .or(void))
165    .parse_stream(input)
166    .into()
167}
168
169fn parse_array<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
170where
171    S::Error: ParseError<char, S::Range, S::Position>,
172{
173    let marker = token('[');
174    (marker, parser(parse_type))
175        .map(|(_, ty)| JavaType::Array(Box::new(ty)))
176        .parse_stream(input)
177        .into()
178}
179
180fn parse_object<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
181where
182    S::Error: ParseError<char, S::Range, S::Position>,
183{
184    let marker = token('L');
185    let end = token(';');
186    let obj = between(marker, end, many1(satisfy(|c| c != ';')));
187
188    obj.map(JavaType::Object).parse_stream(input).into()
189}
190
191fn parse_type<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
192where
193    S::Error: ParseError<char, S::Range, S::Position>,
194{
195    parser(parse_primitive)
196        .map(JavaType::Primitive)
197        .or(parser(parse_array))
198        .or(parser(parse_object))
199        .or(parser(parse_sig))
200        .parse_stream(input)
201        .into()
202}
203
204fn parse_return<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<ReturnType, S>
205where
206    S::Error: ParseError<char, S::Range, S::Position>,
207{
208    parser(parse_primitive)
209        .map(ReturnType::Primitive)
210        .or(parser(parse_array).map(|_| ReturnType::Array))
211        .or(parser(parse_object).map(|_| ReturnType::Object))
212        .parse_stream(input)
213        .into()
214}
215
216fn parse_args<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<Vec<JavaType>, S>
217where
218    S::Error: ParseError<char, S::Range, S::Position>,
219{
220    between(token('('), token(')'), many(parser(parse_type)))
221        .parse_stream(input)
222        .into()
223}
224
225fn parse_sig<S: Stream<Token = char>>(input: &mut S) -> StdParseResult<JavaType, S>
226where
227    S::Error: ParseError<char, S::Range, S::Position>,
228{
229    (parser(parse_args), parser(parse_return))
230        .map(|(a, r)| TypeSignature { args: a, ret: r })
231        .map(|sig| JavaType::Method(Box::new(sig)))
232        .parse_stream(input)
233        .into()
234}
235
236#[cfg(test)]
237mod test {
238    use super::*;
239
240    #[test]
241    fn test_parser() {
242        let inputs = [
243            "(Ljava/lang/String;I)V",
244            "[Lherp;",
245            // fails because the return type does not contain the class name: "(IBVZ)L;"
246            // "(IBVZ)Ljava/lang/String;",
247        ];
248
249        for each in inputs.iter() {
250            let res = JavaType::from_str(each).unwrap();
251            println!("{res:#?}");
252            let s = format!("{res}");
253            assert_eq!(s, *each);
254            let res2 = JavaType::from_str(each).unwrap();
255            println!("{res2:#?}");
256            assert_eq!(res2, res);
257        }
258    }
259
260    #[test]
261    fn test_parser_invalid_signature() {
262        let signature = "()Ljava/lang/List"; // no semicolon
263        let res = JavaType::from_str(signature);
264
265        match res {
266            Ok(any) => {
267                panic!("Unexpected result: {}", any);
268            }
269            Err(err) => {
270                assert!(err.to_string().contains("input: ()Ljava/lang/List"));
271            }
272        }
273    }
274}