flapigen/java_jni/
find_cache.rs

1use rustc_hash::FxHashMap;
2use syn::{
3    parse::{Parse, ParseStream},
4    parse_quote,
5    spanned::Spanned,
6    visit::Visit,
7    LitStr, Macro, Token,
8};
9
10#[derive(Default)]
11pub struct JniCacheMacroCalls {
12    pub calls: FxHashMap<String, JniFindClass>,
13}
14
15impl JniCacheMacroCalls {
16    pub fn global_vars(&self) -> Vec<syn::Item> {
17        let mut ret = Vec::with_capacity(self.calls.len());
18        for find_class in self.calls.values() {
19            let id = &find_class.id;
20            ret.push(parse_quote! {
21                static mut #id: jclass = ::std::ptr::null_mut();
22            });
23            for sub_id in &find_class.methods {
24                let id = &sub_id.id;
25                ret.push(parse_quote! {
26                    static mut #id: jmethodID = ::std::ptr::null_mut();
27                });
28            }
29            for sub_id in &find_class.static_methods {
30                let id = &sub_id.id;
31                ret.push(parse_quote! {
32                    static mut #id: jmethodID = ::std::ptr::null_mut();
33                });
34            }
35            for sub_id in &find_class.static_fields {
36                let id = &sub_id.id;
37                ret.push(parse_quote! {
38                    static mut #id: jfieldID = ::std::ptr::null_mut();
39                });
40            }
41            for sub_id in &find_class.fields {
42                let id = &sub_id.id;
43                ret.push(parse_quote! {
44                    static mut #id: jfieldID = ::std::ptr::null_mut();
45                });
46            }
47        }
48        ret
49    }
50}
51
52#[derive(PartialEq, Debug)]
53pub struct JniFindClass {
54    pub id: syn::Ident,
55    pub path: LitStr,
56    pub methods: Vec<JniClassItemWithId>,
57    pub static_methods: Vec<JniClassItemWithId>,
58    pub static_fields: Vec<JniClassItemWithId>,
59    pub fields: Vec<JniClassItemWithId>,
60}
61impl Parse for JniFindClass {
62    fn parse(input: ParseStream) -> syn::Result<Self> {
63        let id = input.parse()?;
64        input.parse::<Token![,]>()?;
65        let path = input.parse()?;
66        Ok(Self {
67            id,
68            path,
69            methods: Vec::new(),
70            static_methods: Vec::new(),
71            static_fields: Vec::new(),
72            fields: Vec::new(),
73        })
74    }
75}
76
77#[derive(PartialEq, Debug)]
78pub struct JniClassItemWithId {
79    pub id: syn::Ident,
80    pub class_id: syn::Ident,
81    pub name: LitStr,
82    pub sig: LitStr,
83}
84impl Parse for JniClassItemWithId {
85    fn parse(input: ParseStream) -> syn::Result<Self> {
86        let id = input.parse()?;
87        input.parse::<Token![,]>()?;
88        let class_id = input.parse()?;
89        input.parse::<Token![,]>()?;
90        let name = input.parse()?;
91        input.parse::<Token![,]>()?;
92        let sig = input.parse()?;
93        if input.peek(Token![,]) {
94            input.parse::<Token![,]>()?;
95        }
96        Ok(Self {
97            id,
98            class_id,
99            name,
100            sig,
101        })
102    }
103}
104
105macro_rules! add_jni_method_id {
106    ($mac:ident, $calls: ident, $sub_calls: ident, $name: expr) => {
107        let get_method_id: JniClassItemWithId =
108            syn::parse2($mac.tokens.clone()).unwrap_or_else(|err| {
109                panic!(
110                    "Can not parse '{}' call: {}, code: {}",
111                    $name, err, $mac.tokens
112                )
113            });
114        let class_id = get_method_id.class_id.to_string();
115        let find_class = $calls.get_mut(&class_id).unwrap_or_else(|| {
116            panic!(
117                "Can not find class_id for {}({}, {}, ...) call",
118                $name, get_method_id.id, class_id
119            );
120        });
121        if let Some(wrong_usage_pos) = find_class.$sub_calls.iter().position(|elem| {
122            elem.name == get_method_id.name
123                && elem.sig == get_method_id.sig
124                && get_method_id.id != elem.id
125        }) {
126            let prev_get_method_id = &find_class.$sub_calls[wrong_usage_pos];
127            panic!(
128                "{} called twice with different id, {} vs {} for class {}",
129                $name, prev_get_method_id.id, get_method_id.id, class_id
130            );
131        }
132        if !find_class.$sub_calls.iter().any(|x| *x == get_method_id) {
133            find_class.$sub_calls.push(get_method_id);
134        }
135    };
136}
137
138pub struct JniCacheMacroCallsVisitor<'a> {
139    pub inner: &'a mut JniCacheMacroCalls,
140    pub errors: Vec<syn::Error>,
141}
142
143impl<'ast> Visit<'ast> for JniCacheMacroCallsVisitor<'ast> {
144    fn visit_macro(&mut self, mac: &'ast Macro) {
145        static SWIG_JNI_GET_METHOD_ID: &str = "swig_jni_get_method_id";
146        static SWIG_JNI_GET_STATIC_METHOD_ID: &str = "swig_jni_get_static_method_id";
147        static SWIG_JNI_GET_STATIC_FIELD_ID: &str = "swig_jni_get_static_field_id";
148        static SWIG_JNI_GET_FIELD_ID: &str = "swig_jni_get_field_id";
149
150        if mac.path.is_ident("swig_jni_find_class") {
151            let find_class: JniFindClass =
152                syn::parse2(mac.tokens.clone()).expect("Can not parse swig_jni_find_class call");
153            let id = find_class.id.to_string();
154            if let Some(call) = self.inner.calls.get(&id) {
155                if *call != find_class {
156                    println!(
157                        "waring=You use the same id '{}' for different classes '{}' vs '{}'",
158                        id,
159                        call.path.value(),
160                        find_class.path.value()
161                    );
162                    self.errors.push(syn::Error::new(
163                        mac.span(),
164                        format!(
165                            "You use the same id '{}' for different classes '{}' vs '{}'",
166                            id,
167                            call.path.value(),
168                            find_class.path.value()
169                        ),
170                    ));
171                    return;
172                }
173            }
174            self.inner.calls.insert(id, find_class);
175        } else if mac.path.is_ident(SWIG_JNI_GET_METHOD_ID) {
176            let calls = &mut self.inner.calls;
177            add_jni_method_id!(mac, calls, methods, SWIG_JNI_GET_METHOD_ID);
178        } else if mac.path.is_ident(SWIG_JNI_GET_STATIC_METHOD_ID) {
179            let calls = &mut self.inner.calls;
180            add_jni_method_id!(mac, calls, static_methods, SWIG_JNI_GET_STATIC_METHOD_ID);
181        } else if mac.path.is_ident(SWIG_JNI_GET_STATIC_FIELD_ID) {
182            let calls = &mut self.inner.calls;
183            add_jni_method_id!(mac, calls, static_fields, SWIG_JNI_GET_STATIC_FIELD_ID);
184        } else if mac.path.is_ident(SWIG_JNI_GET_FIELD_ID) {
185            let calls = &mut self.inner.calls;
186            add_jni_method_id!(mac, calls, fields, SWIG_JNI_GET_FIELD_ID);
187        }
188        syn::visit::visit_macro(self, mac)
189    }
190}